home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / Pakiet bezpieczenstwa / mini Pentoo LiveCD 2006.1 / mpentoo-2006.1.iso / livecd.squashfs / usr / lib / mozilla-firefox / components / nsExtensionManager.js < prev    next >
Text File  |  2006-05-08  |  268KB  |  7,003 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Extension Manager.
  16.  *
  17.  * The Initial Developer of the Original Code is Ben Goodger.
  18.  * Portions created by the Initial Developer are Copyright (C) 2004
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Ben Goodger <ben@mozilla.org> (Google Inc.)
  23.  *  Benjamin Smedberg <benjamin@smedbergs.us>
  24.  *  Jens Bannmann <jens.b@web.de>
  25.  *  Robert Strong <rob_strong@exchangecode.com>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. //
  42. // TODO:
  43. // - better logging
  44. //
  45.  
  46. const nsIExtensionManager             = Components.interfaces.nsIExtensionManager;
  47. const nsIAddonUpdateCheckListener     = Components.interfaces.nsIAddonUpdateCheckListener;
  48. const nsIUpdateItem                   = Components.interfaces.nsIUpdateItem;
  49. const nsILocalFile                    = Components.interfaces.nsILocalFile;
  50. const nsILineInputStream              = Components.interfaces.nsILineInputStream;
  51. const nsIInstallLocation              = Components.interfaces.nsIInstallLocation;
  52. const nsIURL                          = Components.interfaces.nsIURL
  53. // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  54. // it has been removed will cause a crash on Mac OS X - bug 292823
  55. const nsIDirectoryEnumerator          = Components.interfaces.nsIDirectoryEnumerator;
  56.  
  57. const PREF_EM_APP_EXTENSIONS_VERSION  = "app.extensions.version";
  58. const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
  59. const PREF_UPDATE_COUNT               = "extensions.update.count";
  60. const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
  61. const PREF_EM_IGNOREMTIMECHANGES      = "extensions.ignoreMTimeChanges";
  62. const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
  63. const PREF_EM_LAST_SELECTED_SKIN      = "extensions.lastSelectedSkin";
  64. const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
  65. const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
  66. const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
  67. const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
  68. const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
  69. const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
  70. const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
  71. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
  72. const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
  73. const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
  74. const PREF_XPINSTALL_STATUS_DLG_SKIN  = "xpinstall.dialog.progress.skin";
  75.  
  76. const DIR_EXTENSIONS                  = "extensions";
  77. const DIR_CHROME                      = "chrome";
  78. const DIR_STAGE                       = "staged-xpis";
  79. const FILE_EXTENSIONS                 = "extensions.rdf";
  80. const FILE_EXTENSION_MANIFEST         = "extensions.ini";
  81. const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
  82. const FILE_AUTOREG                    = ".autoreg";
  83. const FILE_INSTALL_MANIFEST           = "install.rdf";
  84. const FILE_CONTENTS_MANIFEST          = "contents.rdf";
  85. const FILE_CHROME_MANIFEST            = "chrome.manifest";
  86.  
  87. const UNKNOWN_XPCOM_ABI               = "unknownABI";
  88.  
  89. const FILE_LOGFILE                    = "extensionmanager.log";
  90.  
  91. const FILE_DEFAULT_THEME_JAR          = "classic.jar";
  92.  
  93. const KEY_PROFILEDIR                  = "ProfD";
  94. const KEY_PROFILEDS                   = "ProfDS";
  95. const KEY_APPDIR                      = "XCurProcD";
  96. const KEY_TEMPDIR                     = "TmpD";
  97.  
  98. const EM_ACTION_REQUESTED_TOPIC       = "em-action-requested";
  99. const EM_ITEM_INSTALLED               = "item-installed";
  100. const EM_ITEM_UPGRADED                = "item-upgraded";
  101. const EM_ITEM_UNINSTALLED             = "item-uninstalled";
  102. const EM_ITEM_ENABLED                 = "item-enabled";
  103. const EM_ITEM_DISABLED                = "item-disabled";
  104. const EM_ITEM_CANCEL                  = "item-cancel-action";
  105.  
  106. const OP_NONE                         = "";
  107. const OP_NEEDS_INSTALL                = "needs-install";
  108. const OP_NEEDS_UPGRADE                = "needs-upgrade";
  109. const OP_NEEDS_UNINSTALL              = "needs-uninstall";
  110. const OP_NEEDS_ENABLE                 = "needs-enable";
  111. const OP_NEEDS_DISABLE                = "needs-disable";
  112.  
  113. const KEY_APP_PROFILE                 = "app-profile";
  114. const KEY_APP_GLOBAL                  = "app-global";
  115.  
  116. const CATEGORY_INSTALL_LOCATIONS      = "extension-install-locations";
  117.  
  118. const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
  119. const PREFIX_NS_CHROME                = "http://www.mozilla.org/rdf/chrome#";
  120. const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
  121. const PREFIX_EXTENSION                = "urn:mozilla:extension:";
  122. const PREFIX_THEME                    = "urn:mozilla:theme:";
  123. const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
  124. const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
  125. const RDFURI_DEFAULT_THEME            = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}";
  126. const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
  127.  
  128. const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
  129. const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
  130. const URI_XPINSTALL_CONFIRM_DIALOG    = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
  131. const URI_FINALIZE_DIALOG             = "chrome://mozapps/content/extensions/finalize.xul";
  132. const URI_EXTENSIONS_PROPERTIES       = "chrome://mozapps/locale/extensions/extensions.properties";
  133. const URI_BRAND_PROPERTIES            = "chrome://branding/locale/brand.properties";
  134. const URI_DOWNLOADS_PROPERTIES        = "chrome://mozapps/locale/downloads/downloads.properties";
  135. const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
  136.  
  137. const INSTALLERROR_SUCCESS               = 0;
  138. const INSTALLERROR_INVALID_VERSION       = -1;
  139. const INSTALLERROR_INVALID_GUID          = -2;
  140. const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
  141. const INSTALLERROR_PHONED_HOME           = -4;
  142. const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
  143.  
  144. const MODE_WRONLY   = 0x02;
  145. const MODE_CREATE   = 0x08;
  146. const MODE_APPEND   = 0x10;
  147. const MODE_TRUNCATE = 0x20;
  148.  
  149. const PERMS_FILE      = 0644;
  150. const PERMS_DIRECTORY = 0755;
  151.  
  152. var gApp  = null;
  153. var gPref = null;
  154. var gRDF  = null;
  155. var gOS   = null;
  156. var gXPCOMABI             = null;
  157. var gOSTarget             = null;
  158. var gConsole              = null;
  159. var gInstallManifestRoot  = null;
  160. var gVersionChecker       = null;
  161. var gLoggingEnabled       = null;
  162.  
  163. /** 
  164.  * Valid GUIDs fit this pattern.
  165.  */
  166. var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
  167.  
  168. /**
  169.  * Creates a Version Checker object.
  170.  * @returns A handle to the global Version Checker service.
  171.  */
  172. function getVersionChecker() {
  173.   if (!gVersionChecker) {
  174.     gVersionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
  175.                                 .getService(Components.interfaces.nsIVersionComparator);
  176.   }
  177.   return gVersionChecker;
  178. }
  179.  
  180. var BundleManager = { 
  181.   /**
  182.   * Creates and returns a String Bundle at the specified URI
  183.   * @param   bundleURI
  184.   *          The URI of the bundle to load
  185.   * @returns A nsIStringBundle which was retrieved.
  186.   */
  187.   getBundle: function(bundleURI) {
  188.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  189.                         .getService(Components.interfaces.nsIStringBundleService);
  190.     return sbs.createBundle(bundleURI);
  191.   },
  192.   
  193.   _appName: "",
  194.   
  195.   /**
  196.    * The Application's display name.
  197.    */
  198.   get appName() {
  199.     if (!this._appName) {
  200.       var brandBundle = this.getBundle(URI_BRAND_PROPERTIES)
  201.       this._appName = brandBundle.GetStringFromName("brandShortName");
  202.     }
  203.     return this._appName;
  204.   }
  205. };
  206.  
  207. ///////////////////////////////////////////////////////////////////////////////
  208. //
  209. // Utility Functions
  210. //
  211. function EM_NS(property) {
  212.   return PREFIX_NS_EM + property;
  213. }
  214.  
  215. function CHROME_NS(property) {
  216.   return PREFIX_NS_CHROME + property;
  217. }
  218.  
  219. function EM_R(property) {
  220.   return gRDF.GetResource(EM_NS(property));
  221. }
  222.  
  223. function EM_L(literal) {
  224.   return gRDF.GetLiteral(literal);
  225. }
  226.  
  227. function EM_I(integer) {
  228.   return gRDF.GetIntLiteral(integer);
  229. }
  230.  
  231. /**
  232.  * Gets a preference value, handling the case where there is no default.
  233.  * @param   func
  234.  *          The name of the preference function to call, on nsIPrefBranch
  235.  * @param   preference
  236.  *          The name of the preference
  237.  * @param   defaultValue
  238.  *          The default value to return in the event the preference has 
  239.  *          no setting
  240.  * @returns The value of the preference, or undefined if there was no
  241.  *          user or default value.
  242.  */
  243. function getPref(func, preference, defaultValue) {
  244.   try {
  245.     return gPref[func](preference);
  246.   }
  247.   catch (e) {
  248.   }
  249.   return defaultValue;
  250. }
  251.  
  252. /**
  253.  * Initializes a RDF Container at a URI in a datasource.
  254.  * @param   datasource
  255.  *          The datasource the container is in
  256.  * @param   root
  257.  *          The RDF Resource which is the root of the container.
  258.  * @returns The nsIRDFContainer, initialized at the root.
  259.  */
  260. function getContainer(datasource, root) {
  261.   var ctr = Components.classes["@mozilla.org/rdf/container;1"]
  262.                       .createInstance(Components.interfaces.nsIRDFContainer);
  263.   ctr.Init(datasource, root);
  264.   return ctr;
  265. }
  266.  
  267. /**
  268.  * Gets a RDF Resource for item with the given ID
  269.  * @param   id
  270.  *          The GUID of the item to construct a RDF resource to the 
  271.  *          active item for
  272.  * @returns The RDF Resource to the Active item. 
  273.  */
  274. function getResourceForID(id) {
  275.   return gRDF.GetResource(PREFIX_ITEM_URI + id);
  276. }
  277.  
  278. /**
  279.  * Construct a nsIUpdateItem with the supplied metadata
  280.  * ...
  281.  */
  282. function makeItem(id, version, locationKey, minVersion, maxVersion, name, 
  283.                   updateURL, updateHash, iconURL, updateRDF, type) {
  284.   var item = Components.classes["@mozilla.org/updates/item;1"]
  285.                        .createInstance(Components.interfaces.nsIUpdateItem);
  286.   item.init(id, version, locationKey, minVersion, maxVersion, name,
  287.             updateURL, updateHash, iconURL, updateRDF, type);
  288.   return item;
  289. }
  290.  
  291. /**
  292.  * Gets the specified directory at the speciifed hierarchy under a 
  293.  * Directory Service key. 
  294.  * @param   key
  295.  *          The Directory Service Key to start from
  296.  * @param   pathArray
  297.  *          An array of path components to locate beneath the directory 
  298.  *          specified by |key|
  299.  * @return  nsIFile object for the location specified. If the directory
  300.  *          requested does not exist, it is created, along with any
  301.  *          parent directories that need to be created.
  302.  */
  303. function getDir(key, pathArray) {
  304.   return getDirInternal(key, pathArray, true);
  305. }
  306.  
  307. /**
  308.  * Gets the specified directory at the speciifed hierarchy under a 
  309.  * Directory Service key. 
  310.  * @param   key
  311.  *          The Directory Service Key to start from
  312.  * @param   pathArray
  313.  *          An array of path components to locate beneath the directory 
  314.  *          specified by |key|
  315.  * @return  nsIFile object for the location specified. If the directory
  316.  *          requested does not exist, it is NOT created.
  317.  */
  318. function getDirNoCreate(key, pathArray) {
  319.   return getDirInternal(key, pathArray, false);
  320. }
  321.  
  322. /**
  323.  * Gets the specified directory at the speciifed hierarchy under a 
  324.  * Directory Service key. 
  325.  * @param   key
  326.  *          The Directory Service Key to start from
  327.  * @param   pathArray
  328.  *          An array of path components to locate beneath the directory 
  329.  *          specified by |key|
  330.  * @param   shouldCreate
  331.  *          true if the directory hierarchy specified in |pathArray|
  332.  *          should be created if it does not exist,
  333.  *          false otherwise.
  334.  * @return  nsIFile object for the location specified. 
  335.  */
  336. function getDirInternal(key, pathArray, shouldCreate) {
  337.   var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
  338.                               .getService(Components.interfaces.nsIProperties);
  339.   var dir = fileLocator.get(key, Components.interfaces.nsIFile);
  340.   for (var i = 0; i < pathArray.length; ++i) {
  341.     dir.append(pathArray[i]);
  342.     if (shouldCreate && !dir.exists())
  343.       dir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  344.   }
  345.   return dir;
  346. }
  347.  
  348. /**
  349.  * Gets the file at the speciifed hierarchy under a Directory Service key.
  350.  * @param   key
  351.  *          The Directory Service Key to start from
  352.  * @param   pathArray
  353.  *          An array of path components to locate beneath the directory 
  354.  *          specified by |key|. The last item in this array must be the
  355.  *          leaf name of a file.
  356.  * @return  nsIFile object for the file specified. The file is NOT created
  357.  *          if it does not exist, however all required directories along 
  358.  *          the way are.
  359.  */
  360. function getFile(key, pathArray) {
  361.   var file = getDir(key, pathArray.slice(0, -1));
  362.   file.append(pathArray[pathArray.length - 1]);
  363.   return file;
  364. }
  365.  
  366. /**
  367.  * Gets the descriptor of a directory as a relative path to common base
  368.  * directories (profile, user home, app install dir, etc).
  369.  *
  370.  * @param   itemLocation
  371.  *          The nsILocalFile representing the item's directory.
  372.  * @param   installLocation the nsIInstallLocation for this item
  373.  */
  374. function getDescriptorFromFile(itemLocation, installLocation) {
  375.   var baseDir = installLocation.location;
  376.  
  377.   if (baseDir && baseDir.contains(itemLocation, true)) {
  378.     return "rel%" + itemLocation.getRelativeDescriptor(baseDir);
  379.   }
  380.  
  381.   return "abs%" + itemLocation.persistentDescriptor;
  382. }
  383.  
  384. function getAbsoluteDescriptor(itemLocation) {
  385.   return itemLocation.persistentDescriptor;
  386. }
  387.  
  388. /**
  389.  * Initializes a Local File object based on a descriptor
  390.  * provided by "getDescriptorFromFile".
  391.  *
  392.  * @param   descriptor
  393.  *          The descriptor that locates the directory
  394.  * @param   installLocation
  395.  *          The nsIInstallLocation object for this item.
  396.  * @returns The nsILocalFile object representing the location of the item
  397.  */
  398. function getFileFromDescriptor(descriptor, installLocation) {
  399.   var location = Components.classes["@mozilla.org/file/local;1"]
  400.                            .createInstance(nsILocalFile);
  401.  
  402.   var m = descriptor.match(/^(abs|rel)\%(.*)$/);
  403.   if (!m)
  404.     throw Components.results.NS_ERROR_INVALID_ARG;
  405.  
  406.   if (m[1] == "rel") {
  407.     location.setRelativeDescriptor(installLocation.location, m[2]);
  408.   }
  409.   else {
  410.     location.persistentDescriptor = m[2];
  411.   }
  412.  
  413.   return location;
  414. }
  415.  
  416. /**
  417.  * Determines if a file URL is an item package - either a XPI or a JAR file.
  418.  * @param   fileURL
  419.  *          The file URL to check
  420.  * @returns true if the URL is an item package, false otherwise.
  421.  */
  422. function fileIsItemPackage(fileURL) {
  423.   var extension = fileURL.fileExtension.toLowerCase();
  424.   return extension == "xpi" || extension == "jar";
  425. }
  426.  
  427. /** 
  428.  * Return the leaf name used by the extension system for staging an item.
  429.  * @param   id
  430.  *          The GUID of the item
  431.  * @param   type
  432.  *          The nsIUpdateItem type of the item
  433.  * @returns The leaf name of the staged file.
  434.  */
  435. function getStagedLeafName(id, type) {
  436.   if (type == nsIUpdateItem.TYPE_THEME) 
  437.     return id + ".jar";
  438.   return id + ".xpi";
  439. }
  440.  
  441. /**
  442.  * Opens a safe file output stream for writing. 
  443.  * @param   file
  444.  *          The file to write to.
  445.  * @param   modeFlags
  446.  *          (optional) File open flags. Can be undefined. 
  447.  * @returns nsIFileOutputStream to write to.
  448.  */
  449. function openSafeFileOutputStream(file, modeFlags) {
  450.   var fos = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
  451.                       .createInstance(Components.interfaces.nsIFileOutputStream);
  452.   if (modeFlags === undefined)
  453.     modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
  454.   if (!file.exists()) 
  455.     file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  456.   fos.init(file, modeFlags, PERMS_FILE, 0);
  457.   return fos;
  458. }
  459.  
  460. /**
  461.  * Closes a safe file output stream.
  462.  * @param   stream
  463.  *          The stream to close.
  464.  */
  465. function closeSafeFileOutputStream(stream) {
  466.   if (stream instanceof Components.interfaces.nsISafeOutputStream)
  467.     stream.finish();
  468.   else
  469.     stream.close();
  470. }
  471.  
  472. /**
  473.  * Logs a string to the error console. 
  474.  * @param   string
  475.  *          The string to write to the error console..
  476.  */  
  477. function LOG(string) {
  478.   if (gLoggingEnabled)  
  479.     dump("*** " + string + "\n");
  480.   gConsole.logStringMessage(string);
  481. }
  482.  
  483. /** 
  484.  * Randomize the specified file name. Used to force RDF to bypass the cache
  485.  * when loading certain types of files.
  486.  * @param   fileName 
  487.  *          A file name to randomize, e.g. install.rdf
  488.  * @returns A randomized file name, e.g. install-xyz.rdf
  489.  */
  490. function getRandomFileName(fileName) {
  491.   var extensionDelimiter = fileName.lastIndexOf(".");
  492.   var prefix = fileName.substr(0, extensionDelimiter);
  493.   var suffix = fileName.substr(extensionDelimiter);
  494.   
  495.   var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
  496.   var nameString = prefix + "-";
  497.   for (var i = 0; i < 3; ++i) {
  498.     var index = Math.round((Math.random()) * characters.length);
  499.     nameString += characters.charAt(index);
  500.   }
  501.   return nameString + "." + suffix;
  502. }
  503.  
  504. /**
  505.  * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used
  506.  * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource 
  507.  * are NOT prefixed.
  508.  * @param   type
  509.  *          The nsIUpdateItem type to find a RDF URI prefix for
  510.  * @returns The RDF URI prefix.
  511.  */
  512. function getItemPrefix(type) {
  513.   if (type & nsIUpdateItem.TYPE_EXTENSION) 
  514.     return PREFIX_EXTENSION;
  515.   else if (type & nsIUpdateItem.TYPE_THEME)
  516.     return PREFIX_THEME;
  517.   return PREFIX_ITEM_URI;
  518. }
  519.  
  520. /**
  521.  * Trims a prefix from a string.
  522.  * @param   string
  523.  *          The source string
  524.  * @param   prefix
  525.  *          The prefix to remove.
  526.  * @returns The suffix (string - prefix)
  527.  */
  528. function stripPrefix(string, prefix) {
  529.   return string.substr(prefix.length);
  530. }
  531.  
  532. /**
  533.  * Gets a File URL spec for a nsIFile
  534.  * @param   file
  535.  *          The file to get a file URL spec to
  536.  * @returns The file URL spec to the file
  537.  */
  538. function getURLSpecFromFile(file) {
  539.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  540.                          .getService(Components.interfaces.nsIIOService);
  541.   var fph = ioServ.getProtocolHandler("file")
  542.                   .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  543.   return fph.getURLSpecFromFile(file);
  544. }
  545.  
  546. /**
  547.  * Constructs a URI to a spec.
  548.  * @param   spec
  549.  *          The spec to construct a URI to
  550.  * @returns The nsIURI constructed.
  551.  */
  552. function newURI(spec) {
  553.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  554.                          .getService(Components.interfaces.nsIIOService);
  555.   return ioServ.newURI(spec, null, null);
  556. }
  557.  
  558. /** 
  559.  * Constructs a File URI to a nsIFile
  560.  * @param   file
  561.  *          The file to construct a File URI to
  562.  * @returns The file URI to the file
  563.  */
  564. function getURIFromFile(file) {
  565.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  566.                          .getService(Components.interfaces.nsIIOService);
  567.   return ioServ.newFileURI(file);
  568. }
  569.  
  570. /**
  571.  * @returns Whether or not we are currently running in safe mode.
  572.  */
  573. function inSafeMode() {
  574.   return gApp.inSafeMode;
  575. }
  576.  
  577. /**
  578.  * Extract the string value from a RDF Literal or Resource
  579.  * @param   literalOrResource
  580.  *          RDF String Literal or Resource
  581.  * @returns String value of the literal or resource, or undefined if the object
  582.  *          supplied is not a RDF string literal or resource.
  583.  */
  584. function stringData(literalOrResource) {
  585.   if (literalOrResource instanceof Components.interfaces.nsIRDFLiteral)
  586.     return literalOrResource.Value;
  587.   if (literalOrResource instanceof Components.interfaces.nsIRDFResource)
  588.     return literalOrResource.Value;
  589.   return undefined;
  590. }
  591.  
  592. /**
  593.  * Extract the integer value of a RDF Literal
  594.  * @param   literal
  595.  *          nsIRDFInt literal
  596.  * @return  integer value of the literal
  597.  */
  598. function intData(literal) {
  599.   if (literal instanceof Components.interfaces.nsIRDFInt)
  600.     return literal.Value;
  601.   return undefined;
  602. }
  603.  
  604. /**
  605.  * Gets a property from an install manifest.
  606.  * @param   installManifest
  607.  *          An Install Manifest datasource to read from
  608.  * @param   property
  609.  *          The name of a proprety to read (sans EM_NS)
  610.  * @returns The literal value of the property, or undefined if the property has
  611.  *          no value.
  612.  */
  613. function getManifestProperty(installManifest, property) {
  614.   var target = installManifest.GetTarget(gInstallManifestRoot, 
  615.                                          gRDF.GetResource(EM_NS(property)), true);
  616.   var val = stringData(target);
  617.   return val === undefined ? intData(target) : val;
  618. }
  619.  
  620. /**
  621.  * Given an Install Manifest Datasource, retrieves the type of item the manifest
  622.  * describes.
  623.  * @param   installManifest 
  624.  *          The Install Manifest Datasource.
  625.  * @return  The nsIUpdateItem type of the item described by the manifest
  626.  *          returns TYPE_EXTENSION if attempts to determine the type fail.
  627.  */
  628. function getAddonTypeFromInstallManifest(installManifest) {
  629.   var typeArc = gRDF.GetResource(EM_NS("type"));
  630.   var type = intData(installManifest.GetTarget(gInstallManifestRoot, typeArc, true));
  631.   if (type !== undefined)
  632.     return type;
  633.  
  634.   // Firefox 1.0 and earlier did not support addon-type annotation on the 
  635.   // Install Manifest, so we fall back to a theme-only property to 
  636.   // differentiate.
  637.   if (getManifestProperty(installManifest, "internalName") !== undefined)
  638.     return nsIUpdateItem.TYPE_THEME;
  639.  
  640.   // If no type is provided, default to "Extension"
  641.   return nsIUpdateItem.TYPE_EXTENSION;    
  642. }
  643.  
  644. /**
  645.  * Shows a message about an incompatible Extension/Theme. 
  646.  * @param   installData
  647.  *          An Install Data object from |getInstallData|
  648.  */
  649. function showIncompatibleError(installData) {
  650.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  651.   var params = [extensionStrings.GetStringFromName("type-" + installData.type)];
  652.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  653.                                                     params, params.length);
  654.   var message;
  655.   var targetAppData = installData.currentApp;
  656.   if (!targetAppData) {
  657.     params = [installData.name, installData.version, BundleManager.appName];
  658.     message = extensionStrings.formatStringFromName("incompatibleMessageNoApp", 
  659.                                                     params, params.length);
  660.   }
  661.   else if (targetAppData.minVersion == targetAppData.maxVersion) {
  662.     // If the min target app version and the max target app version are the same, don't show
  663.     // a message like, "Foo is only compatible with Firefox versions 0.7 to 0.7", rather just
  664.     // show, "Foo is only compatible with Firefox 0.7"
  665.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  666.               installData.name, installData.version, BundleManager.appName, 
  667.               targetAppData.minVersion];
  668.     message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion", 
  669.                                                     params, params.length);
  670.   }
  671.   else {
  672.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  673.               installData.name, installData.version, BundleManager.appName, 
  674.               targetAppData.minVersion, targetAppData.maxVersion];
  675.     message = extensionStrings.formatStringFromName("incompatibleMsg", params, params.length);
  676.   }
  677.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  678.                      .getService(Components.interfaces.nsIPromptService);
  679.   ps.alert(null, title, message);
  680. }
  681.  
  682. /**
  683.  * Shows a message.
  684.  * @param   titleKey
  685.  *          String key of the title string in the Extensions localization file.
  686.  * @param   messageKey
  687.  *          String key of the message string in the Extensions localization file.
  688.  * @param   messageParams
  689.  *          Array of strings to be substituted into |messageKey|. Can be null.
  690.  */
  691. function showMessage(titleKey, titleParams, messageKey, messageParams) {
  692.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  693.   if (titleParams && titleParams.length > 0) {
  694.     var title = extensionStrings.formatStringFromName(titleKey, titleParams,
  695.                                                       titleParams.length);
  696.   }
  697.   else
  698.     title = extensionStrings.GetStringFromName(titleKey);
  699.  
  700.   if (messageParams && messageParams.length > 0) {
  701.     var message = extensionStrings.formatStringFromName(messageKey, messageParams,
  702.                                                         messageParams.length);
  703.   }
  704.   else
  705.     message = extensionStrings.GetStringFromName(messageKey);
  706.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  707.                      .getService(Components.interfaces.nsIPromptService);
  708.   ps.alert(null, title, message);
  709. }
  710.  
  711. /** 
  712.  * Gets a zip reader for the file specified.
  713.  * @param   zipFile
  714.  *          A ZIP archive to open with a nsIZipReader.
  715.  * @return  A nsIZipReader for the file specified.
  716.  */
  717. function getZipReaderForFile(zipFile) {
  718.   try {
  719.     var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  720.                               .createInstance(Components.interfaces.nsIZipReader);
  721.     zipReader.init(zipFile);
  722.     zipReader.open();
  723.   }
  724.   catch (e) {
  725.     zipReader.close();
  726.     throw e;
  727.   }
  728.   return zipReader;
  729. }
  730.  
  731. /** 
  732.  * Extract a RDF file from a ZIP archive to a random location in the system
  733.  * temp directory.
  734.  * @param   zipFile
  735.  *          A ZIP archive to read from
  736.  * @param   fileName 
  737.  *          The name of the file to read from the zip. 
  738.  * @param   suppressErrors
  739.  *          Whether or not to report errors. 
  740.  * @return  The file created in the temp directory.
  741.  */
  742. function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) {
  743.   var file = null;
  744.   try {
  745.     var zipReader = getZipReaderForFile(zipFile);
  746.     zipReader.getEntry(fileName);
  747.     
  748.     file = getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]);
  749.     zipReader.extract(fileName, file);
  750.     zipReader.close();
  751.   }
  752.   catch (e) {
  753.     // always close the zip reader even if we throw
  754.     if (!suppressErrors) {
  755.       showMessage("missingFileTitle", [], "missingFileMessage", 
  756.                   [BundleManager.appName, fileName]);
  757.       throw e;
  758.     }
  759.   }
  760.   return file;
  761. }
  762.  
  763. /**
  764.  * Show a message to the user informing them they are installing an old non-EM
  765.  * style Theme, and that these are not supported.
  766.  * @param   installManifest 
  767.  *          The Old-Style Contents Manifest datasource representing the theme. 
  768.  */
  769. function showOldThemeError(contentsManifest) {
  770.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  771.   var params = [extensionStrings.GetStringFromName("theme")];
  772.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  773.                                                     params, params.length);
  774.   var appVersion = extensionStrings.GetStringFromName("incompatibleOlder");
  775.   
  776.   try {  
  777.     var ctr = getContainer(contentsManifest, 
  778.                            gRDF.GetResource("urn:mozilla:skin:root"));
  779.     var elts = ctr.GetElements();
  780.     var nameArc = gRDF.GetResource(CHROME_NS("displayName"));
  781.     while (elts.hasMoreElements()) {
  782.       var elt = elts.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  783.       themeName = stringData(contentsManifest.GetTarget(elt, nameArc, true));
  784.       if (themeName) 
  785.         break;
  786.     }
  787.   }
  788.   catch (e) {
  789.     themeName = extensionStrings.GetStringFromName("incompatibleThemeName");
  790.   }
  791.   
  792.   params = [themeName, "", BundleManager.appName, gApp.version, themeName, "", 
  793.             BundleManager.appName, appVersion];
  794.   var message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion",
  795.                                                       params, params.length);
  796.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  797.                      .getService(Components.interfaces.nsIPromptService);
  798.   ps.alert(null, title, message);
  799. }
  800.  
  801. /**
  802.  * Gets an Install Manifest datasource from a file.
  803.  * @param   file
  804.  *          The nsIFile that contains the Install Manifest RDF
  805.  * @returns The Install Manifest datasource
  806.  */
  807. function getInstallManifest(file) {
  808.   var fileURL = getURLSpecFromFile(file);
  809.   var ds = gRDF.GetDataSourceBlocking(fileURL);
  810.   var arcs = ds.ArcLabelsOut(gInstallManifestRoot);
  811.   if (!arcs.hasMoreElements()) {
  812.     ds = null;
  813.     var uri = Components.classes["@mozilla.org/network/io-service;1"]
  814.                         .getService(Components.interfaces.nsIIOService)
  815.                         .newFileURI(file);
  816.     var url = uri.QueryInterface(nsIURL);
  817.     showMessage("malformedTitle", [], "malformedMessage", 
  818.                 [BundleManager.appName, url.fileName]);
  819.   }
  820.   return ds;
  821. }
  822.  
  823. /**
  824.  * An enumeration of items in a JS array.
  825.  * @constructor
  826.  */
  827. function ArrayEnumerator(aItems) {
  828.   this._index = 0;
  829.   if (aItems) {
  830.     for (var i = 0; i < aItems.length; ++i) {
  831.       if (!aItems[i])
  832.         aItems.splice(i, 1);      
  833.     }
  834.   }
  835.   this._contents = aItems;
  836. }
  837.  
  838. ArrayEnumerator.prototype = {
  839.   _index: 0,
  840.   _contents: [],
  841.   
  842.   hasMoreElements: function() {
  843.     return this._index < this._contents.length;
  844.   },
  845.   
  846.   getNext: function() {
  847.     return this._contents[this._index++];      
  848.   }
  849. };
  850.  
  851. /**
  852.  * An enumeration of files in a JS array.
  853.  * @param   files
  854.  *          The files to enumerate
  855.  * @constructor
  856.  */
  857. function FileEnumerator(files) {
  858.   this._index = 0;
  859.   if (files) {
  860.     for (var i = 0; i < files.length; ++i) {
  861.       if (!files[i])
  862.         files.splice(i, 1);      
  863.     }
  864.   }
  865.   this._contents = files;
  866. }
  867.  
  868. FileEnumerator.prototype = {
  869.   _index: 0,
  870.   _contents: [],
  871.  
  872.   /**
  873.    * Gets the next file in the sequence.
  874.    */  
  875.   get nextFile() {
  876.     if (this._index < this._contents.length)
  877.       return this._contents[this._index++];
  878.     return null;
  879.   },
  880.   
  881.   /**
  882.    * Stop enumerating. Nothing to do here.
  883.    */
  884.   close: function() {
  885.   },
  886. };
  887.  
  888. /**
  889.  * An object which identifies an Install Location for items, where the location
  890.  * relationship is each item living in a directory named with its GUID under 
  891.  * the directory used when constructing this object.
  892.  *
  893.  * e.g. <location>\{GUID1}
  894.  *      <location>\{GUID2}
  895.  *      <location>\{GUID3}
  896.  *      ...
  897.  *
  898.  * @param   name
  899.  *          The string identifier of this Install Location.
  900.  * @param   location
  901.  *          The directory that contains the items. 
  902.  * @constructor
  903.  */
  904. function DirectoryInstallLocation(name, location, restricted, priority) {
  905.   this._name = name;
  906.   if (location.exists()) {
  907.     if (!location.isDirectory())
  908.       throw new Error("location must be a directoy!");
  909.   }
  910.   else {
  911.     try {
  912.       location.create(nsILocalFile.DIRECTORY_TYPE, 0775);
  913.     }
  914.     catch (e) {
  915.       LOG("DirectoryInstallLocation: failed to create location " + 
  916.           " directory = " + location.path + ", exception = " + e + "\n");
  917.     }
  918.   }
  919.  
  920.   this._location = location;
  921.   this._locationToIDMap = {};
  922.   this._restricted = restricted;
  923.   this._priority = priority;
  924. }
  925. DirectoryInstallLocation.prototype = {
  926.   _name           : "",
  927.   _location       : null,
  928.   _locationToIDMap: null,
  929.   _restricted     : false,
  930.   _priority       : 0,
  931.   _canAccess      : null,
  932.   
  933.   /**
  934.    * See nsIExtensionManager.idl
  935.    */
  936.   get name() {
  937.     return this._name;
  938.   },
  939.   
  940.   /**
  941.    * Reads a directory linked to in a file.
  942.    * @param   file
  943.    *          The file containing the directory path
  944.    * @returns A nsILocalFile object representing the linked directory.
  945.    */
  946.   _readDirectoryFromFile: function(file) {
  947.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  948.                         .createInstance(Components.interfaces.nsIFileInputStream);
  949.     fis.init(file, -1, -1, false);
  950.     var line = { value: "" };
  951.     if (fis instanceof nsILineInputStream)
  952.       fis.readLine(line);
  953.     fis.close();
  954.     if (line.value) {
  955.       var linkedDirectory = Components.classes["@mozilla.org/file/local;1"]
  956.                                       .createInstance(nsILocalFile);
  957.       try {
  958.         linkedDirectory.initWithPath(line.value);
  959.       }
  960.       catch (e) {
  961.         linkedDirectory.setRelativeDescriptor(file.parent, line.value);
  962.       }
  963.       
  964.       return linkedDirectory;
  965.     }
  966.     return null;
  967.   },
  968.   
  969.   /**
  970.    * See nsIExtensionManager.idl
  971.    */
  972.   get itemLocations() {
  973.     var locations = [];
  974.     if (!this._location.exists())
  975.       return new FileEnumerator(locations);
  976.     
  977.     try {
  978.       var entries = this._location.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  979.       while (true) {
  980.         var entry = entries.nextFile;
  981.         if (!entry)
  982.           break;
  983.         entry instanceof nsILocalFile;
  984.         if (!entry.isDirectory() && gIDTest.test(entry.leafName)) {
  985.           var linkedDirectory = this._readDirectoryFromFile(entry);
  986.           if (linkedDirectory && linkedDirectory.exists() && 
  987.               linkedDirectory.isDirectory()) {
  988.             locations.push(linkedDirectory);
  989.             this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName;
  990.           }
  991.         }
  992.         else
  993.           locations.push(entry);
  994.       }
  995.       entries.close();
  996.     }
  997.     catch (e) { 
  998.     }
  999.     return new FileEnumerator(locations);
  1000.   },
  1001.   
  1002.   /**
  1003.    * Retrieves the GUID for an item at the specified location.
  1004.    * @param   file
  1005.    *          The location where an item might live.
  1006.    * @returns The ID for an item that might live at the location specified.
  1007.    * 
  1008.    * N.B. This function makes no promises about whether or not this path is 
  1009.    *      actually maintained by this Install Location.
  1010.    */
  1011.   getIDForLocation: function(file) {
  1012.     var section = file.leafName;
  1013.     var filePD = file.persistentDescriptor;
  1014.     if (filePD in this._locationToIDMap) 
  1015.       section = this._locationToIDMap[filePD];
  1016.     
  1017.     if (gIDTest.test(section))
  1018.       return RegExp.$1;
  1019.     return undefined;
  1020.   },
  1021.   
  1022.   /**
  1023.    * See nsIExtensionManager.idl
  1024.    */
  1025.   get location() {
  1026.     return this._location.clone();
  1027.   },
  1028.   
  1029.   /**
  1030.    * See nsIExtensionManager.idl
  1031.    */
  1032.   get restricted() {
  1033.     return this._restricted;
  1034.   },
  1035.   
  1036.   /**
  1037.    * See nsIExtensionManager.idl
  1038.    */
  1039.   get canAccess() {
  1040.     if (this._canAccess != null)
  1041.       return this._canAccess;
  1042.  
  1043.     var testFile = this.location;
  1044.     testFile.append("Access Privileges Test");
  1045.     try {
  1046.       testFile.createUnique(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1047.       testFile.remove(false);
  1048.       this._canAccess = true;
  1049.     }
  1050.     catch (e) {
  1051.       this._canAccess = false;
  1052.     }
  1053.     return this._canAccess;
  1054.   },
  1055.   
  1056.   /**
  1057.    * See nsIExtensionManager.idl
  1058.    */
  1059.   get priority() {
  1060.     return this._priority;
  1061.   },
  1062.   
  1063.   /**
  1064.    * See nsIExtensionManager.idl
  1065.    */
  1066.   getItemLocation: function(id) {
  1067.     var itemLocation = this.location;
  1068.     itemLocation.append(id);
  1069.     if (itemLocation.exists() && !itemLocation.isDirectory())
  1070.       return this._readDirectoryFromFile(itemLocation);
  1071.     if (!itemLocation.exists() && this.canAccess)
  1072.       itemLocation.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1073.     return itemLocation;
  1074.   },
  1075.   
  1076.   /**
  1077.    * See nsIExtensionManager.idl
  1078.    */
  1079.   itemIsManagedIndependently: function(id) {
  1080.     var itemLocation = this.location;
  1081.     itemLocation.append(id);
  1082.     return itemLocation.exists() && !itemLocation.isDirectory();      
  1083.   },
  1084.   
  1085.   /**
  1086.    * See nsIExtensionManager.idl
  1087.    */
  1088.   getItemFile: function(id, filePath) {
  1089.     var itemLocation = this.getItemLocation(id).clone();
  1090.     var parts = filePath.split("/");
  1091.     for (var i = 0; i < parts.length; ++i)
  1092.       itemLocation.append(parts[i]);
  1093.     return itemLocation;
  1094.   },
  1095.  
  1096.   /**
  1097.    * Stages the specified file for later.
  1098.    * @param   file
  1099.    *          The file to stage
  1100.    * @param   id
  1101.    *          The GUID of the item the file represents
  1102.    */
  1103.   stageFile: function(file, id) {
  1104.     var stagedFile = this.location;
  1105.     stagedFile.append(DIR_STAGE);
  1106.     stagedFile.append(id);
  1107.     stagedFile.append(file.leafName);
  1108.  
  1109.     // When an incompatible update is successful the file is already staged
  1110.     if (stagedFile.equals(file))
  1111.       return stagedFile;
  1112.  
  1113.     if (stagedFile.exists()) 
  1114.       stagedFile.remove(false);
  1115.       
  1116.     file.copyTo(stagedFile.parent, stagedFile.leafName);
  1117.     
  1118.     // If the file has incorrect permissions set, correct them now.
  1119.     if (!stagedFile.isWritable())
  1120.       stagedFile.permissions = PERMS_FILE;
  1121.     
  1122.     return stagedFile;
  1123.   },
  1124.   
  1125.   getStageFile: function(id) {
  1126.     var stageDir = this.location;
  1127.     stageDir.append(DIR_STAGE);
  1128.     stageDir.append(id);
  1129.     if (!stageDir.exists() || !stageDir.isDirectory())
  1130.       return null;
  1131.     try {
  1132.       var entries = stageDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1133.       var stageFile = entries.nextFile;
  1134.       stageFile instanceof nsILocalFile;
  1135.       entries.close();
  1136.     }
  1137.     catch (e) {
  1138.     }
  1139.     return stageFile;
  1140.   },
  1141.   
  1142.   /**
  1143.    * Removes a file from the stage. This cleans up the stage if there is nothing
  1144.    * else left after the remove operation.
  1145.    * @param   file
  1146.    *          The file to remove.
  1147.    */
  1148.   removeFile: function(file) {
  1149.     if (file.exists())
  1150.       file.remove(false);
  1151.     var parent = file.parent;
  1152.     var entries = parent.directoryEntries;    
  1153.     try {
  1154.       // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  1155.       // it has been removed will cause a crash on Mac OS X - bug 292823
  1156.       while (parent && !parent.equals(this.location) &&
  1157.             !entries.hasMoreElements()) {
  1158.         parent.remove(false);
  1159.         parent = parent.parent;
  1160.         entries = parent.directoryEntries;
  1161.       }
  1162.       if (entries instanceof nsIDirectoryEnumerator)
  1163.         entries.close();
  1164.     }
  1165.     catch (e) {
  1166.       LOG("DirectoryInstallLocation::removeFile: failed to remove staged " + 
  1167.           " directory = " + parent.path + ", exception = " + e + "\n");
  1168.     }
  1169.   },
  1170.   
  1171.   /**
  1172.    * See nsISupports.idl
  1173.    */
  1174.   QueryInterface: function (iid) {
  1175.     if (!iid.equals(Components.interfaces.nsIInstallLocation) &&
  1176.         !iid.equals(Components.interfaces.nsISupports))
  1177.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1178.     return this;
  1179.   }
  1180. };
  1181.  
  1182. //@line 1333 "/var/tmp/portage/mozilla-firefox-1.5.0.3/work/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1183.  
  1184. /**
  1185.  * An object which handles the installation of an Extension.
  1186.  * @constructor
  1187.  */
  1188. function Installer(ds, id, installLocation, type) {
  1189.   this._ds = ds;
  1190.   this._id = id;
  1191.   this._type = type;
  1192.   this._installLocation = installLocation;
  1193.   this._metadataFile = this._installLocation
  1194.                            .getItemFile(this._id, FILE_INSTALL_MANIFEST);
  1195. }
  1196. Installer.prototype = {
  1197.   // Item metadata
  1198.   _id: null,
  1199.   _ds: null,
  1200.   _installLocation: null,
  1201.   _metadataDS: null,
  1202.   
  1203.   /**
  1204.    * Gets the Install Manifest datasource we are installing from.
  1205.    */
  1206.   get metadataDS() {
  1207.     if (!this._metadataDS) {
  1208.       if (!this._metadataFile.exists()) 
  1209.         return null;
  1210.       this._metadataDS = getInstallManifest(this._metadataFile);
  1211.       if (!this._metadataDS) {
  1212.         LOG("Installer::install: metadata datasource for extension " + 
  1213.             this._id + " at " + this._metadataFile.path + " could not be loaded. " + 
  1214.             " Installation will not proceed.");
  1215.       }
  1216.     }
  1217.     return this._metadataDS;
  1218.   },
  1219.   
  1220.   /**
  1221.    * Installs the Extension
  1222.    * @param   file
  1223.    *          A XPI/JAR file to install from. If this is null or does not exist,
  1224.    *          the item is assumed to be an expanded directory, located at the GUID
  1225.    *          key in the supplied Install Location.
  1226.    */
  1227.   installFromFile: function(file) {
  1228.     // Move files from the staging dir into the extension's final home.
  1229.     if (file && file.exists()) {
  1230.       this._installExtensionFiles(file);
  1231.     }
  1232.  
  1233.     if (!this.metadataDS)
  1234.       return;
  1235.  
  1236.     // Upgrade old-style contents.rdf Chrome Manifests if necessary.
  1237.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1238.       this.upgradeThemeChrome();
  1239.     else
  1240.       this.upgradeExtensionChrome();
  1241.  
  1242.     // Add metadata for the extension to the global extension metadata set
  1243.     this._ds.addItemMetadata(this._id, this.metadataDS, this._installLocation);
  1244.   },
  1245.   
  1246.   /**
  1247.    * Safely extract the Extension's files into the target folder.
  1248.    * @param   file
  1249.    *          The XPI/JAR file to install from.
  1250.    */
  1251.   _installExtensionFiles: function(file) {
  1252.     var installer = this;
  1253.     /**
  1254.       * Callback for |safeInstallOperation| that performs file level installation
  1255.       * steps for an Extension.
  1256.       * @param   extensionID
  1257.       *          The GUID of the Extension being installed.
  1258.       * @param   installLocation 
  1259.       *          The Install Location where the Extension is being installed.
  1260.       * @param   xpiFile
  1261.       *          The source XPI file that contains the Extension.
  1262.       */
  1263.     function extractExtensionFiles(extensionID, installLocation, xpiFile) {
  1264.       // Create a logger to log install operations for uninstall. This must be 
  1265.       // created in the |safeInstallOperation| callback, since it creates a file
  1266.       // in the target directory. If we do this outside of the callback, we may
  1267.       // be clobbering a file we should not be.
  1268.       var zipReader = getZipReaderForFile(xpiFile);
  1269.       
  1270.       // create directories first
  1271.       var entries = zipReader.findEntries("*/");
  1272.       while (entries.hasMoreElements()) {
  1273.         var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1274.         var target = installLocation.getItemFile(extensionID, entry.name);
  1275.         if (!target.exists()) {
  1276.           try {
  1277.             target.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1278.           }
  1279.           catch (e) {
  1280.             LOG("extractExtensionsFiles: failed to create target directory for extraction " + 
  1281.                 " file = " + target.path + ", exception = " + e + "\n");
  1282.           }
  1283.         }
  1284.       }
  1285.  
  1286.       entries = zipReader.findEntries("*");
  1287.       while (entries.hasMoreElements()) {
  1288.         entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1289.         target = installLocation.getItemFile(extensionID, entry.name);
  1290.         if (target.exists())
  1291.           continue;
  1292.  
  1293.         try {
  1294.           target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1295.         }
  1296.         catch (e) {
  1297.           LOG("extractExtensionsFiles: failed to create target file for extraction " + 
  1298.               " file = " + target.path + ", exception = " + e + "\n");
  1299.         }
  1300.         zipReader.extract(entry.name, target);
  1301.       }
  1302.       zipReader.close();
  1303.     }
  1304.  
  1305.     var installer = this;
  1306.     /**
  1307.       * Callback for |safeInstallOperation| that performs file level installation
  1308.       * steps for a Theme.
  1309.       * @param   id
  1310.       *          The GUID of the Theme being installed.
  1311.       * @param   installLocation 
  1312.       *          The Install Location where the Theme is being installed.
  1313.       * @param   jarFile
  1314.       *          The source JAR file that contains the Theme.
  1315.       */
  1316.     function extractThemeFiles(id, installLocation, jarFile) {
  1317.       var themeDirectory = installLocation.getItemLocation(id);
  1318.       var zipReader = getZipReaderForFile(jarFile);
  1319.  
  1320.       // The only critical file is the install.rdf and we would not have
  1321.       // gotten this far without one.
  1322.       var rootFiles = [FILE_INSTALL_MANIFEST, FILE_CHROME_MANIFEST,
  1323.                        "preview.png", "icon.png"];
  1324.       for (var i = 0; i < rootFiles.length; ++i) {
  1325.         try {
  1326.           var entry = zipReader.getEntry(rootFiles[i]);
  1327.           var target = installLocation.getItemFile(id, rootFiles[i]);
  1328.           zipReader.extract(rootFiles[i], target);
  1329.         }
  1330.         catch (e) {
  1331.         }
  1332.       }
  1333.  
  1334.       var manifestFile = installLocation.getItemFile(id, FILE_CHROME_MANIFEST);
  1335.       // new theme structure requires a chrome.manifest file
  1336.       if (manifestFile.exists()) {
  1337.         var entries = zipReader.findEntries(DIR_CHROME + "/*");
  1338.         while (entries.hasMoreElements()) {
  1339.           entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1340.           if (entry.name.substr(entry.name.length - 1, 1) == "/")
  1341.             continue;
  1342.           target = installLocation.getItemFile(id, entry.name);
  1343.           try {
  1344.             target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1345.           }
  1346.           catch (e) {
  1347.             LOG("extractThemeFiles: failed to create target file for extraction " + 
  1348.                 " file = " + target.path + ", exception = " + e + "\n");
  1349.           }
  1350.           zipReader.extract(entry.name, target);
  1351.         }
  1352.         zipReader.close();
  1353.       }
  1354.       else { // old theme structure requires only an install.rdf
  1355.         try {
  1356.           var entry = zipReader.getEntry(FILE_CONTENTS_MANIFEST);
  1357.           var contentsManifestFile = installLocation.getItemFile(id, FILE_CONTENTS_MANIFEST);
  1358.           contentsManifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1359.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  1360.         }
  1361.         catch (e) {
  1362.           zipReader.close();
  1363.           LOG("extractThemeFiles: failed to extract contents.rdf: " + target.path);
  1364.           throw e; // let the safe-op clean up
  1365.         }
  1366.         zipReader.close();
  1367.         var chromeDir = installLocation.getItemFile(id, DIR_CHROME);
  1368.         try {
  1369.           jarFile.copyTo(chromeDir, jarFile.fileName);
  1370.         }
  1371.         catch (e) {
  1372.           LOG("extractThemeFiles: failed to copy theme JAR file to: " + chromeDir.path);
  1373.           throw e; // let the safe-op clean up
  1374.         }
  1375.  
  1376.         if (!installer.metadataDS && installer._type == nsIUpdateItem.TYPE_THEME) {
  1377.           if (contentsManifestFile && contentsManifestFile.exists()) {
  1378.             var contentsManifest = gRDF.GetDataSourceBlocking(getURLSpecFromFile(contentsManifestFile));
  1379.             showOldThemeError(contentsManifest);
  1380.           }
  1381.           LOG("Theme JAR file: " + jarFile.leafName + " contains an Old-Style " + 
  1382.               "Theme that is not compatible with this version of the software.");
  1383.           throw new Error("Old Theme"); // let the safe-op clean up
  1384.         }
  1385.       }
  1386.     }
  1387.  
  1388.     var callback = extractExtensionFiles;
  1389.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1390.       callback = extractThemeFiles;
  1391.     safeInstallOperation(this._id, this._installLocation,
  1392.                           { callback: callback, data: file });
  1393.   },
  1394.   
  1395.   /** 
  1396.    * Upgrade contents.rdf Chrome Manifests used by this Theme to the new 
  1397.    * chrome.manifest format if necessary.
  1398.    */
  1399.   upgradeThemeChrome: function() {
  1400.     // Use the Chrome Registry API to install the theme there
  1401.     var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1402.                        .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1403.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1404.     if (manifestFile.exists() ||
  1405.         this._id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  1406.       return;
  1407.  
  1408.     try {
  1409.       // creates a chrome manifest for themes
  1410.       var manifestURI = getURIFromFile(manifestFile);
  1411.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1412.       // We're relying on the fact that there is only one JAR file
  1413.       // in the "chrome" directory. This is a hack, but it works.
  1414.       var entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1415.       var jarFile = entries.nextFile;
  1416.       if (jarFile) {
  1417.         var jarFileURI = getURIFromFile(jarFile);
  1418.         var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  1419.         var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1420.         var contentsFileURI = getURIFromFile(contentsFile.parent);
  1421.  
  1422.         cr.processContentsManifest(contentsFileURI, manifestURI, contentsURI, false, true);
  1423.       }
  1424.       entries.close();
  1425.       contentsFile.remove(false);
  1426.     }
  1427.     catch (e) {
  1428.       // Failed to register chrome, for any number of reasons - non-existent 
  1429.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1430.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1431.       // extension is uninstalled properly during the subsequent uninstall 
  1432.       // pass in |ExtensionManager::_finalizeOperations|
  1433.       LOG("upgradeThemeChrome: failed for theme " + this._id + " - why " + 
  1434.           "not convert to the new chrome.manifest format while you're at it? " + 
  1435.           "Failure exception: " + e);
  1436.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1437.                   [BundleManager.appName]);
  1438.  
  1439.       var stageFile = this._installLocation.getStageFile(this._id);
  1440.       if (stageFile)
  1441.         this._installLocation.removeFile(stageFile);
  1442.  
  1443.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1444.       StartupCache.write();
  1445.     }
  1446.   },
  1447.  
  1448.   /** 
  1449.    * Upgrade contents.rdf Chrome Manifests used by this Extension to the new 
  1450.    * chrome.manifest format if necessary.
  1451.    */
  1452.   upgradeExtensionChrome: function() {
  1453.     // If the extension is aware of the new flat chrome manifests and has 
  1454.     // included one, just use it instead of generating one from the
  1455.     // install.rdf/contents.rdf data.
  1456.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1457.     if (manifestFile.exists())
  1458.       return;
  1459.  
  1460.     try {
  1461.       // Enumerate the metadata datasource files collection and register chrome
  1462.       // for each file, calling _registerChrome for each.
  1463.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1464.       
  1465.       if (!manifestFile.parent.exists())
  1466.         return;
  1467.  
  1468.       // Even if an extension doesn't have any chrome, we generate an empty
  1469.       // manifest file so that we don't try to upgrade from the "old-style"
  1470.       // chrome manifests at every startup.
  1471.       manifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1472.  
  1473.       var manifestURI = getURIFromFile(manifestFile);
  1474.       var files = this.metadataDS.GetTargets(gInstallManifestRoot, EM_R("file"), true);
  1475.       while (files.hasMoreElements()) {
  1476.         var file = files.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  1477.         var chromeFile = chromeDir.clone();
  1478.         var fileName = file.Value.substr("urn:mozilla:extension:file:".length, file.Value.length);
  1479.         chromeFile.append(fileName);
  1480.  
  1481.         var fileURLSpec = getURLSpecFromFile(chromeFile);
  1482.         if (!chromeFile.isDirectory()) {
  1483.           var zipReader = getZipReaderForFile(chromeFile);
  1484.           fileURLSpec = "jar:" + fileURLSpec + "!/";
  1485.           var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1486.           contentsFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1487.         }
  1488.  
  1489.         var providers = [EM_R("package"), EM_R("skin"), EM_R("locale")];
  1490.         for (var i = 0; i < providers.length; ++i) {
  1491.           var items = this.metadataDS.GetTargets(file, providers[i], true);
  1492.           while (items.hasMoreElements()) {
  1493.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFLiteral);
  1494.             var fileURI = newURI(fileURLSpec + item.Value);
  1495.             // Extract the contents.rdf files instead of opening them inside of
  1496.             // the jar. This prevents the jar from being cached by the zip
  1497.             // reader which will keep the jar in use and prevent deletion.
  1498.             if (zipReader) {
  1499.               zipReader.extract(item.Value + FILE_CONTENTS_MANIFEST, contentsFile);
  1500.               var contentsFileURI = getURIFromFile(contentsFile.parent);
  1501.             }
  1502.             else
  1503.               contentsFileURI = fileURI;
  1504.  
  1505.             var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1506.                                .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1507.             cr.processContentsManifest(contentsFileURI, manifestURI, fileURI, true, false);
  1508.           }
  1509.         }
  1510.         if (zipReader) {
  1511.           zipReader.close();
  1512.           zipReader = null;
  1513.           contentsFile.remove(false);
  1514.         }
  1515.       }
  1516.     }
  1517.     catch (e) {
  1518.       // Failed to register chrome, for any number of reasons - non-existent 
  1519.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1520.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1521.       // extension is uninstalled properly during the subsequent uninstall 
  1522.       // pass in |ExtensionManager::_finalizeOperations|
  1523.       LOG("upgradeExtensionChrome: failed for extension " + this._id + " - why " + 
  1524.           "not convert to the new chrome.manifest format while you're at it? " + 
  1525.           "Failure exception: " + e);
  1526.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1527.                   [BundleManager.appName]);
  1528.  
  1529.       var stageFile = this._installLocation.getStageFile(this._id);
  1530.       if (stageFile)
  1531.         this._installLocation.removeFile(stageFile);
  1532.  
  1533.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1534.       StartupCache.write();
  1535.     }
  1536.   }  
  1537. };
  1538.  
  1539. /**
  1540.  * Safely attempt to perform a caller-defined install operation for a given
  1541.  * item ID. Using aggressive success-safety checks, this function will attempt
  1542.  * to move an existing location for an item aside and then allow installation
  1543.  * into the appropriate folder. If any operation fails the installation will 
  1544.  * abort and roll back from the moved-aside old version.
  1545.  * @param   itemID
  1546.  *          The GUID of the item to perform the operation on.
  1547.  * @param   installLocation
  1548.  *          The Install Location where the item is installed.
  1549.  * @param   installCallback
  1550.  *          A caller supplied JS object with the following properties:
  1551.  *          "data"      A data parameter to be passed to the callback.
  1552.  *          "callback"  A function to perform the install operation. This
  1553.  *                      function is passed three parameters:
  1554.  *                      1. The GUID of the item being operated on.
  1555.  *                      2. The Install Location where the item is installed.
  1556.  *                      3. The "data" parameter on the installCallback object.
  1557.  */
  1558. function safeInstallOperation(itemID, installLocation, installCallback) {
  1559.   var movedFiles = [];
  1560.   
  1561.   /**
  1562.    * Reverts a deep move by moving backed up files back to their original
  1563.    * location.
  1564.    */
  1565.   function rollbackMove()
  1566.   {
  1567.     for (var i = 0; i < movedFiles.length; ++i) {
  1568.       var oldFile = movedFiles[i].oldFile;
  1569.       var newFile = movedFiles[i].newFile;
  1570.       try {
  1571.         newFile.moveTo(oldFile.parent, newFile.leafName);
  1572.       }
  1573.       catch (e) {
  1574.         LOG("safeInstallOperation: failed to roll back files after an install " + 
  1575.             "operation failed. Failed to roll back: " + newFile.path + " to: " + 
  1576.             oldFile.path + " ... aborting installation.");
  1577.         throw e;
  1578.       }
  1579.     }
  1580.   }
  1581.   
  1582.   /**
  1583.    * Moves a file to a new folder.
  1584.    * @param   file
  1585.    *          The file to move
  1586.    * @param   destination
  1587.    *          The target folder
  1588.    */
  1589.   function moveFile(file, destination) {
  1590.     try {
  1591.       var oldFile = file.clone();
  1592.       file.moveTo(destination, file.leafName);
  1593.       movedFiles.push({ oldFile: oldFile, newFile: file });
  1594.     }
  1595.     catch (e) {
  1596.       LOG("safeInstallOperation: failed to back up file: " + file.path + " to: " + 
  1597.           destination.path + " ... rolling back file moves and aborting " + 
  1598.           "installation.");
  1599.       rollbackMove();
  1600.       throw e;
  1601.     }
  1602.   }
  1603.   
  1604.   /**
  1605.    * Moves a directory to a new location. If any part of the move fails,
  1606.    * files already moved will be rolled back.
  1607.    * @param   sourceDir
  1608.    *          The directory to move
  1609.    * @param   targetDir
  1610.    *          The destination directory
  1611.    * @param   currentDir
  1612.    *          The current directory (a subdirectory of |sourceDir| or 
  1613.    *          |sourceDir| itself) we are moving files from.
  1614.    */
  1615.   function moveDirectory(sourceDir, targetDir, currentDir) {
  1616.     var entries = currentDir.directoryEntries;
  1617.     try {
  1618.       while (true) {
  1619.         var entry = entries.nextFile;
  1620.         if (!entry)
  1621.           break;
  1622.         if (entry.isDirectory())
  1623.           moveDirectory(sourceDir, targetDir, entry);
  1624.         else {
  1625.           if (entry instanceof nsILocalFile) {
  1626.             var rd = entry.getRelativeDescriptor(sourceDir);
  1627.             var destination = targetDir.clone().QueryInterface(nsILocalFile);
  1628.             destination.setRelativeDescriptor(targetDir, rd);
  1629.             moveFile(entry, destination.parent);
  1630.           }
  1631.         }
  1632.       }
  1633.       if (entries instanceof nsIDirectoryEnumerator)
  1634.         entries.close();
  1635.     }
  1636.     catch (e) {
  1637.     }
  1638.   }
  1639.   
  1640.   /**
  1641.    * Removes the temporary backup directory where we stored files. 
  1642.    * @param   directory
  1643.    *          The backup directory to remove
  1644.    */
  1645.   function cleanUpTrash(directory) {
  1646.     try {
  1647.       // Us-generated. Safe.
  1648.       if (directory && directory.exists())
  1649.         directory.remove(true);
  1650.     }
  1651.     catch (e) {
  1652.       LOG("safeInstallOperation: failed to clean up the temporary backup of the " + 
  1653.           "older version: " + itemLocationTrash.path);
  1654.       // This is a non-fatal error. Annoying, but non-fatal. 
  1655.     }
  1656.   }
  1657.   
  1658.   var itemLocation = installLocation.getItemLocation(itemID);
  1659.   if (itemLocation.exists()) {
  1660.     var trashDirName = itemID + "-trash";
  1661.     var itemLocationTrash = itemLocation.parent.clone();
  1662.     itemLocationTrash.append(trashDirName);
  1663.     if (itemLocationTrash.exists()) {
  1664.       // We can remove recursively here since this is a folder we created, not
  1665.       // one the user specified. If this fails, it'll throw, and the caller 
  1666.       // should stop installation.
  1667.       try {
  1668.         itemLocationTrash.remove(true);
  1669.       }
  1670.       catch (e) {
  1671.         LOG("safeFileOperation: failed to remove existing trash directory " + 
  1672.             itemLocationTrash.path + " ... aborting installation.");
  1673.         throw e;
  1674.       }
  1675.     }
  1676.     
  1677.     // Move the directory that contains the existing version of the item aside, 
  1678.     // into {GUID}-trash. This will throw if there's a failure and the install
  1679.     // will abort.
  1680.     moveDirectory(itemLocation, itemLocationTrash, itemLocation);
  1681.     
  1682.     // Clean up the original location, if necessary. Again, this is a path we 
  1683.     // generated, so it is safe to recursively delete.
  1684.     try {
  1685.       itemLocation.remove(true);
  1686.     }
  1687.     catch (e) {
  1688.       LOG("safeInstallOperation: failed to clean up item location after its contents " + 
  1689.           "were properly backed up. Failed to clean up: " + itemLocation.path + 
  1690.           " ... rolling back file moves and aborting installation.");
  1691.       rollbackMove();
  1692.       cleanUpTrash(itemLocationTrash);
  1693.       throw e;
  1694.     }
  1695.   }
  1696.       
  1697.   // Now tell the client to do their stuff.
  1698.   try {
  1699.     installCallback.callback(itemID, installLocation, installCallback.data);
  1700.   }
  1701.   catch (e) {
  1702.     // This means the install operation failed. Remove everything and roll back.
  1703.     LOG("safeInstallOperation: install operation (caller-supplied callback) failed, " + 
  1704.         "rolling back file moves and aborting installation.");
  1705.     try {
  1706.       // Us-generated. Safe.
  1707.       itemLocation.remove(true);
  1708.     }
  1709.     catch (e) {
  1710.       LOG("safeInstallOperation: failed to remove the folder we failed to install " + 
  1711.           "an item into: " + itemLocation.path + " -- There is not much to suggest " + 
  1712.           "here... maybe restart and try again?");
  1713.       cleanUpTrash(itemLocationTrash);
  1714.       throw e;
  1715.     }
  1716.     rollbackMove();
  1717.     cleanUpTrash(itemLocationTrash);
  1718.     throw e;        
  1719.   }
  1720.   
  1721.   // Now, and only now - after everything else has succeeded (against all odds!) 
  1722.   // remove the {GUID}-trash directory where we stashed the old version of the 
  1723.   // item.
  1724.   cleanUpTrash(itemLocationTrash);
  1725. }
  1726.  
  1727. /**
  1728.  * Manages the list of pending operations.
  1729.  */
  1730. var PendingOperations = {
  1731.   _ops: { },
  1732.  
  1733.   /**
  1734.    * Adds an entry to the Pending Operations List
  1735.    * @param   opType
  1736.    *          The type of Operation to be performed
  1737.    * @param   entry
  1738.    *          A JS Object representing the item to be operated on:
  1739.    *          "locationKey"   The name of the Install Location where the item
  1740.    *                          is installed.
  1741.    *          "id"            The GUID of the item.
  1742.    */
  1743.   addItem: function(opType, entry) {
  1744.     if (!(opType in this._ops))
  1745.       this._ops[opType] = [];
  1746.     this._ops[opType].push(entry);
  1747.   },
  1748.   
  1749.   /**
  1750.    * Removes a Pending Operation from the list
  1751.    * @param   opType
  1752.    *          The type of Operation being removed
  1753.    * @param   id
  1754.    *          The GUID of the item to remove the entry for
  1755.    */
  1756.   clearItem: function(opType, id) {
  1757.     if (!(opType in this._ops))
  1758.       return;
  1759.     for (var i = 0; i < this._ops[opType].length; ++i) {
  1760.       if (this._ops[opType][i].id == id)
  1761.         this._ops[opType].splice(i, 1);
  1762.     }
  1763.   },
  1764.   
  1765.   /**
  1766.    * Remove all Pending Operations of a certain type
  1767.    * @param   opType
  1768.    *          The type of Operation to remove all entries for
  1769.    */
  1770.   clearItems: function(opType) {
  1771.     if (opType in this._ops)
  1772.       this._ops[opType] = [];
  1773.   },
  1774.   
  1775.   /**
  1776.    * Get an array of operations of a certain type
  1777.    * @param   opType
  1778.    *          The type of Operation to return a list of
  1779.    */
  1780.   getOperations: function(opType) {
  1781.     return opType in this._ops ? this._ops[opType] : [];
  1782.   },
  1783.   
  1784.   /**
  1785.    * The total number of Pending Operations, for all types.
  1786.    */
  1787.   get size() {
  1788.     var size = 0;
  1789.     for (var opType in this._ops)
  1790.       size += this._ops[opType].length;
  1791.     return size;
  1792.   }
  1793. };
  1794.  
  1795. /**
  1796.  * Manages registered Install Locations
  1797.  */
  1798. var InstallLocations = { 
  1799.   _locations: { },
  1800.  
  1801.   /**
  1802.    * A nsISimpleEnumerator of all available Install Locations.
  1803.    */
  1804.   get enumeration() {
  1805.     var installLocations = [];
  1806.     for (var key in this._locations) 
  1807.       installLocations.push(InstallLocations.get(key));
  1808.     return new ArrayEnumerator(installLocations);
  1809.   },
  1810.   
  1811.   /**
  1812.    * Gets a named Install Location
  1813.    * @param   name
  1814.    *          The name of the Install Location to get
  1815.    */
  1816.   get: function(name) {
  1817.     return name in this._locations ? this._locations[name] : null;
  1818.   },
  1819.   
  1820.   /**
  1821.    * Registers an Install Location
  1822.    * @param   installLocation
  1823.    *          The Install Location to register
  1824.    */
  1825.   put: function(installLocation) {
  1826.     this._locations[installLocation.name] = installLocation;
  1827.   }
  1828. };
  1829.  
  1830. /**
  1831.  * Manages the Startup Cache. The Startup Cache is a representation
  1832.  * of the contents of extensions.cache, a list of all
  1833.  * items the Extension System knows about, whether or not they
  1834.  * are active or visible.
  1835.  */
  1836. var StartupCache = {
  1837.   /**
  1838.    * Location Name -> GUID hash of entries from the Startup Cache file
  1839.    * Each entry has the following properties:
  1840.    *  "descriptor"    The location on disk of the item
  1841.    *  "mtime"         The time the location was last modified
  1842.    *  "op"            Any pending operations on this item.
  1843.    *  "location"      The Install Location name where the item is installed.
  1844.    */
  1845.   entries: { },
  1846.  
  1847.   /**
  1848.    * Puts an entry into the Startup Cache
  1849.    * @param   installLocation
  1850.    *          The Install Location where the item is installed
  1851.    * @param   id
  1852.    *          The GUID of the item
  1853.    * @param   op
  1854.    *          The name of the operation to be performed
  1855.    * @param   shouldCreate
  1856.    *          Whether or not we should create a new entry for this item
  1857.    *          in the cache if one does not already exist. 
  1858.    */
  1859.   put: function(installLocation, id, op, shouldCreate) {
  1860.     var itemLocation = installLocation.getItemLocation(id);
  1861.  
  1862.     var descriptor = null;
  1863.     var mtime = null;
  1864.     if (itemLocation) {
  1865.       itemLocation.QueryInterface(nsILocalFile);
  1866.       descriptor = getDescriptorFromFile(itemLocation, installLocation);
  1867.       if (itemLocation.exists() && itemLocation.isDirectory())
  1868.         mtime = Math.floor(itemLocation.lastModifiedTime / 1000);
  1869.     }
  1870.  
  1871.     this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate);
  1872.   },
  1873.  
  1874.   /**
  1875.    * Private helper function for putting an entry into the Startup Cache
  1876.    * without relying on the presence of its associated nsIInstallLocation
  1877.    * instance.
  1878.    *
  1879.    * @param key
  1880.    *        The install location name.
  1881.    * @param id
  1882.    *        The ID of the item.
  1883.    * @param descriptor
  1884.    *        Value returned from absoluteDescriptor.  May be null, in which
  1885.    *        case the descriptor field is not updated.
  1886.    * @param mtime
  1887.    *        The last modified time of the item.  May be null, in which case the
  1888.    *        descriptor field is not updated.
  1889.    * @param op
  1890.    *        The OP code to store with the entry.
  1891.    * @param shouldCreate
  1892.    *        Boolean value indicating whether to create or delete the entry.
  1893.    */
  1894.   _putRaw: function(key, id, descriptor, mtime, op, shouldCreate) {
  1895.     if (!(key in this.entries))
  1896.       this.entries[key] = { };
  1897.     if (!(id in this.entries[key]))
  1898.       this.entries[key][id] = { };
  1899.     if (shouldCreate) {
  1900.       if (!this.entries[key][id]) 
  1901.         this.entries[key][id] = { };
  1902.  
  1903.       var entry = this.entries[key][id];
  1904.  
  1905.       if (descriptor)
  1906.         entry.descriptor = descriptor;
  1907.       if (mtime) 
  1908.         entry.mtime = mtime;
  1909.       entry.op = op;
  1910.       entry.location = key;
  1911.     }
  1912.     else
  1913.       this.entries[key][id] = null;
  1914.   },
  1915.   
  1916.   /**
  1917.    * Clears an entry from the Startup Cache
  1918.    * @param   installLocation
  1919.    *          The Install Location where item is installed
  1920.    * @param   id
  1921.    *          The GUID of the item.
  1922.    */
  1923.   clearEntry: function(installLocation, id) {
  1924.     var key = installLocation.name;
  1925.     if (key in this.entries && id in this.entries[key])
  1926.       this.entries[key][id] = null;
  1927.   },
  1928.   
  1929.   /**
  1930.    * Get all the startup cache entries for a particular ID.
  1931.    * @param   id
  1932.    *          The GUID of the item to locate.
  1933.    * @returns An array of Startup Cache entries for the specified ID.
  1934.    */
  1935.   findEntries: function(id) {
  1936.     var entries = [];
  1937.     for (var key in this.entries) {
  1938.       if (id in this.entries[key]) 
  1939.         entries.push(this.entries[key][id]);
  1940.     }
  1941.     return entries;
  1942.   },
  1943.  
  1944.   /**
  1945.    * Call a function on each entry.  The callback function takes a single
  1946.    * parameter, which is an entry object.
  1947.    */
  1948.   forEachEntry: function(callback) {
  1949.     for (var key in this.entries) {
  1950.       for (id in this.entries[key])
  1951.         callback(this.entries[key][id]);
  1952.     }
  1953.   },
  1954.   
  1955.   /** 
  1956.    * Read the Item-Change manifest file into a hash of properties.
  1957.    * The Item-Change manifest currently holds a list of paths, with the last
  1958.    * mtime for each path, and the GUID of the item at that path.
  1959.    */
  1960.   read: function() {
  1961.     var itemChangeManifest = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  1962.     if (!itemChangeManifest.exists()) {
  1963.       // There is no change manifest for some reason, either we're in an initial
  1964.       // state or something went wrong with one of the other files and the
  1965.       // change manifest was removed. Return an empty dataset and rebuild.
  1966.       return;
  1967.     }
  1968.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  1969.                         .createInstance(Components.interfaces.nsIFileInputStream);
  1970.     fis.init(itemChangeManifest, -1, -1, false);
  1971.     if (fis instanceof nsILineInputStream) {
  1972.       var line = { value: "" };
  1973.       var more = false;
  1974.       do {
  1975.         more = fis.readLine(line);
  1976.         if (line.value) {
  1977.           // The Item-Change manifest is formatted like so:
  1978.           //  (pd = descriptor)
  1979.           // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  1980.           // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op
  1981.           // ...
  1982.           // We hash on location-key first, because we don't want to have to 
  1983.           // spin up the main extensions datasource on every start to determine
  1984.           // the Install Location for an item.
  1985.           // We hash on guid second, because we want a way to quickly determine
  1986.           // item GUID during a check loop that runs on every startup.
  1987.           var parts = line.value.split("\t");
  1988.           var op = parts[4];
  1989.           this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true);
  1990.           if (op)
  1991.             PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] });
  1992.         }
  1993.       }
  1994.       while (more);
  1995.     }
  1996.     fis.close();
  1997.   },
  1998.  
  1999.   /**
  2000.    * Writes the Startup Cache to disk
  2001.    */
  2002.   write: function() {
  2003.     var extensionsCacheFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2004.     var fos = openSafeFileOutputStream(extensionsCacheFile);
  2005.     for (var locationKey in this.entries) {
  2006.       for (var id in this.entries[locationKey]) {
  2007.         var entry = this.entries[locationKey][id];
  2008.         if (entry) {
  2009.           try {
  2010.             var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey));
  2011.  
  2012.             // Update our knowledge of this item's last-modified-time.
  2013.             // XXXdarin: this may cause us to miss changes in some cases.
  2014.             var itemMTime = 0;
  2015.             if (itemLocation.exists() && itemLocation.isDirectory())
  2016.               itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2017.  
  2018.             // Each line in the startup cache manifest is in this form:
  2019.             // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2020.             var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" +
  2021.                        itemMTime + "\t" + entry.op + "\r\n";
  2022.             fos.write(line, line.length);
  2023.           }
  2024.           catch (e) {}
  2025.         }
  2026.       }
  2027.     }
  2028.     closeSafeFileOutputStream(fos);
  2029.   }
  2030. };
  2031.  
  2032. /**
  2033.  * Installs, manages and tracks compatibility for Extensions and Themes
  2034.  * @constructor
  2035.  */
  2036. function ExtensionManager() {
  2037.   gApp = Components.classes["@mozilla.org/xre/app-info;1"]
  2038.                    .getService(Components.interfaces.nsIXULAppInfo)
  2039.                    .QueryInterface(Components.interfaces.nsIXULRuntime);
  2040.   gOSTarget = gApp.OS;
  2041.   try {
  2042.     gXPCOMABI = gApp.XPCOMABI;
  2043.   } catch (ex) {
  2044.     // Provide a default for gXPCOMABI. It won't be compared to an
  2045.     // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
  2046.     // as targetPlatform), but it will be displayed in error messages and
  2047.     // transmitted to update URLs.
  2048.     gXPCOMABI = UNKNOWN_XPCOM_ABI;
  2049.   }
  2050.   gPref = Components.classes["@mozilla.org/preferences-service;1"]
  2051.                     .getService(Components.interfaces.nsIPrefBranch2);
  2052.   gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2053.   gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
  2054.  
  2055.   gOS = Components.classes["@mozilla.org/observer-service;1"]
  2056.                   .getService(Components.interfaces.nsIObserverService);
  2057.   gOS.addObserver(this, "xpcom-shutdown", false);
  2058.  
  2059.   gConsole = Components.classes["@mozilla.org/consoleservice;1"]
  2060.                        .getService(Components.interfaces.nsIConsoleService);  
  2061.   
  2062.   gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  2063.                    .getService(Components.interfaces.nsIRDFService);
  2064.   gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
  2065.   
  2066.   // Register Global Install Location
  2067.   var appGlobalExtensions = getDirNoCreate(KEY_APPDIR, [DIR_EXTENSIONS]);
  2068.   var priority = nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
  2069.   var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL, 
  2070.                                                     appGlobalExtensions, true,
  2071.                                                     priority);
  2072.   InstallLocations.put(globalLocation);
  2073.  
  2074.   // Register App-Profile Install Location
  2075.   var appProfileExtensions = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS]);
  2076.   var priority = nsIInstallLocation.PRIORITY_APP_PROFILE;
  2077.   var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE, 
  2078.                                                      appProfileExtensions, false,
  2079.                                                      priority);
  2080.   InstallLocations.put(profileLocation);
  2081.  
  2082. //@line 2247 "/var/tmp/portage/mozilla-firefox-1.5.0.3/work/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2083.  
  2084.   // Register Additional Install Locations
  2085.   var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  2086.                                   .getService(Components.interfaces.nsICategoryManager);
  2087.   var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS);
  2088.   while (locations.hasMoreElements()) {
  2089.     var entry = locations.getNext().QueryInterface(Components.interfaces.nsISupportsCString).data;
  2090.     var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry);
  2091.     var location = Components.classes[contractID].getService(nsIInstallLocation);
  2092.     InstallLocations.put(location);
  2093.   }
  2094. }
  2095.  
  2096. ExtensionManager.prototype = {
  2097.   /**
  2098.    * See nsIObserver.idl
  2099.    */
  2100.   observe: function(subject, topic, data) {
  2101.     switch (topic) {
  2102.     case "app-startup":
  2103.       gOS.addObserver(this, "profile-after-change", false);
  2104.       break;
  2105.     case "profile-after-change":
  2106.       this._profileSelected();
  2107.       break;
  2108.     case "quit-application-requested":
  2109.       this._confirmCancelDownloadsOnQuit(subject);
  2110.       break;
  2111.     case "offline-requested":
  2112.       this._confirmCancelDownloadsOnOffline(subject);
  2113.       break;
  2114.     case "xpcom-shutdown":
  2115.       this._shutdown();
  2116.       break;
  2117.     case "nsPref:changed":
  2118.       if (data == PREF_EM_LOGGING_ENABLED)
  2119.         this._loggingToggled();
  2120.       break;
  2121.     }
  2122.   },
  2123.   
  2124.   /**
  2125.    * Refresh the logging enabled global from preferences when the user changes
  2126.    * the preference settting.
  2127.    */
  2128.   _loggingToggled: function() {
  2129.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2130.   },
  2131.  
  2132.   /**
  2133.    * Initialize the system after a profile has been selected.
  2134.    */  
  2135.   _profileSelected: function() {
  2136.     // Tell the Chrome Registry which Skin to select
  2137.     try {
  2138.       if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
  2139.         var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  2140.         gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
  2141.         gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
  2142.         gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  2143.       }
  2144.     }
  2145.     catch (e) {
  2146.     }
  2147.   },
  2148.   
  2149.   /**
  2150.    * Clean up on application shutdown to avoid leaks.
  2151.    */
  2152.   _shutdown: function() {
  2153.     gOS.removeObserver(this, "xpcom-shutdown");    
  2154.  
  2155.     // Release strongly held services.
  2156.     gOS = null;
  2157.     if (this._ds && gRDF) 
  2158.       gRDF.UnregisterDataSource(this._ds)
  2159.     gRDF = null;
  2160.     if (gPref)
  2161.       gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
  2162.     gPref = null;
  2163.     gConsole = null;
  2164.     gVersionChecker = null;
  2165.     gInstallManifestRoot = null;
  2166.     gApp = null;
  2167.   },
  2168.   
  2169.   /**
  2170.    * Check for presence of critical Extension system files. If any is missing, 
  2171.    * delete the others and signal that the system needs to rebuild them all
  2172.    * from scratch.
  2173.    * @returns true if any critical file is missing and the system needs to
  2174.    *          be rebuilt, false otherwise.
  2175.    */
  2176.   _ensureDatasetIntegrity: function () {
  2177.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  2178.     var extensionsINI = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2179.     var extensionsCache = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2180.     
  2181.     var dsExists = extensionsDS.exists();
  2182.     var iniExists = extensionsINI.exists();
  2183.     var cacheExists = extensionsCache.exists();
  2184.  
  2185.     if (dsExists && iniExists && cacheExists)
  2186.       return false;
  2187.  
  2188.     // If any of the files are missing, remove the .ini file
  2189.     if (iniExists)
  2190.       extensionsINI.remove(false);
  2191.  
  2192.     // If the extensions datasource is missing remove the .cache file if it exists
  2193.     if (!dsExists && cacheExists)
  2194.       extensionsCache.remove(false);
  2195.  
  2196.     return true;
  2197.   },
  2198.   
  2199.   /**
  2200.    * See nsIExtensionManager.idl
  2201.    */
  2202.   start: function(commandLine) {
  2203.     var isDirty = false;
  2204.     var forceAutoReg = false;
  2205.     
  2206.     // Somehow the component list went away, and for that reason the new one
  2207.     // generated by this function is going to result in a different compreg.
  2208.     // We must force a restart.
  2209.     var componentList = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2210.     if (!componentList.exists())
  2211.       forceAutoReg = true;
  2212.     
  2213.     // Check for missing manifests - e.g. missing extensions.ini, missing
  2214.     // extensions.cache, extensions.rdf etc. If any of these files 
  2215.     // is missing then we are in some kind of weird or initial state and need
  2216.     // to force a regeneration.
  2217.     if (this._ensureDatasetIntegrity())
  2218.       isDirty = true;
  2219.  
  2220.     // Configure any items that are being installed, uninstalled or upgraded 
  2221.     // by being added, removed or modified by another process. We must do this
  2222.     // on every startup since there is no way we can tell if this has happened
  2223.     // or not!
  2224.     if (this._checkForFileChanges())
  2225.       isDirty = true;
  2226.  
  2227.     if (PendingOperations.size != 0)
  2228.       isDirty = true;
  2229.  
  2230.     // Extension Changes
  2231.     if (isDirty) {
  2232.       var needsRestart = this._finishOperations();
  2233.  
  2234.       if (forceAutoReg) {
  2235.         this._extensionListChanged = true;
  2236.         needsRestart = true;
  2237.       }
  2238.       return needsRestart;
  2239.     }
  2240.       
  2241.     this._startTimers();
  2242.  
  2243.     return false;
  2244.   },
  2245.   
  2246.   /**
  2247.    * Begins all background update check timers
  2248.    */
  2249.   _startTimers: function() {
  2250.     // Register a background update check timer
  2251.     var tm = 
  2252.         Components.classes["@mozilla.org/updates/timer-manager;1"]
  2253.                   .getService(Components.interfaces.nsIUpdateTimerManager);
  2254.     var interval = getPref("getIntPref", PREF_EM_UPDATE_INTERVAL, 86400); 
  2255.     tm.registerTimer("addon-background-update-timer", this, interval);
  2256.   },
  2257.   
  2258.   /**
  2259.    * Notified when a timer fires
  2260.    * @param   timer
  2261.    *          The timer that fired
  2262.    */
  2263.   notify: function(timer) {
  2264.     if (getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
  2265.       this.update([], 0, false, null);
  2266.   },
  2267.   
  2268.   /**
  2269.    * See nsIExtensionManager.idl
  2270.    */
  2271.   handleCommandLineArgs: function(commandLine) {
  2272.     try {
  2273.       var globalExtension = commandLine.handleFlagWithParam("install-global-extension", false);
  2274.       if (globalExtension) {
  2275.         var file = commandLine.resolveFile(globalExtension);
  2276.         this._installGlobalItem(file);
  2277.       }
  2278.       var globalTheme = commandLine.handleFlagWithParam("install-global-theme", false);
  2279.       if (globalTheme) {
  2280.         file = commandLine.resolveFile(globalTheme);
  2281.         this._installGlobalItem(file);
  2282.       }
  2283.     }
  2284.     catch (e) { 
  2285.       LOG("ExtensionManager:handleCommandLineArgs - failure, catching exception - lineno: " +
  2286.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  2287.     }
  2288.     commandLine.preventDefault = true;
  2289.   },
  2290.  
  2291.   /**
  2292.    * Installs an XPI/JAR file into the KEY_APP_GLOBAL install location.
  2293.    * @param   file
  2294.    *          The XPI/JAR file to extract
  2295.    */
  2296.   _installGlobalItem: function(file) {
  2297.     if (!file || !file.exists())
  2298.       throw new Error("Unable to find the file specified on the command line!");
  2299.     var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2300.     if (!installManifestFile || !installManifestFile.exists())
  2301.       throw new Error("The package is missing an install manifest!");
  2302.     var installManifest = getInstallManifest(installManifestFile);
  2303.     installManifestFile.remove(false);
  2304.     var installData = this._getInstallData(installManifest);
  2305.     var installer = new Installer(installManifest, installData.id,
  2306.                                   InstallLocations.get(KEY_APP_GLOBAL),
  2307.                                   installData.type);
  2308.     installer._installExtensionFiles(file);
  2309.     if (installData.type == nsIUpdateItem.TYPE_EXTENSION)
  2310.       installer.upgradeExtensionChrome();
  2311.     else
  2312.       installer.upgradeThemeChrome();
  2313.   },
  2314.  
  2315.   /**
  2316.    * Check to see if a file is a XPI/JAR file that the user dropped into this
  2317.    * Install Location. (i.e. a XPI that is not a staged XPI from an install 
  2318.    * transaction that is currently in operation). 
  2319.    * @param   file
  2320.    *          The XPI/JAR file to configure
  2321.    * @param   location
  2322.    *          The Install Location where this file was found.
  2323.    * @returns A nsIUpdateItem representing the dropped XPI if this file was a 
  2324.    *          XPI/JAR that needs installation, null otherwise.
  2325.    */
  2326.   _getItemForDroppedFile: function(file, location) {
  2327.     var fileURL = getURIFromFile(file);
  2328.     if (fileURL instanceof nsIURL) {
  2329.       if (fileIsItemPackage(fileURL)) {
  2330.         // We know nothing about this item, it is not something we've
  2331.         // staged in preparation for finalization, so assume it's something
  2332.         // the user dropped in.
  2333.         LOG("A Item Package appeared at: " + file.path + " that we know " + 
  2334.             "nothing about, assuming it was dropped in by the user and " + 
  2335.             "configuring for installation now. Location Key: " + location.name);
  2336.  
  2337.         var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2338.         if (!installManifestFile.exists())
  2339.           return null;
  2340.         var installManifest = getInstallManifest(installManifestFile);
  2341.         installManifestFile.remove(false);
  2342.         var ds = this.datasource;
  2343.         var installData = this._getInstallData(installManifest);
  2344.         var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest);
  2345.         return makeItem(installData.id,
  2346.                         installData.version,
  2347.                         location.name,
  2348.                         targetAppInfo ? targetAppInfo.minVersion : "",
  2349.                         targetAppInfo ? targetAppInfo.maxVersion : "",
  2350.                         getManifestProperty(installManifest, "name"),
  2351.                         "", /* XPI Update URL */
  2352.                         "", /* XPI Update Hash */
  2353.                         getManifestProperty(installManifest, "iconURL"),
  2354.                         getManifestProperty(installManifest, "updateURL"),
  2355.                         installData.type);
  2356.       }
  2357.     }
  2358.     return null;
  2359.   },
  2360.   
  2361.   /**
  2362.    * Check for changes to items that were made independently of the Extension 
  2363.    * Manager, e.g. items were added or removed from a Install Location or items
  2364.    * in an Install Location changed. 
  2365.    */
  2366.   _checkForFileChanges: function() {
  2367.     var em = this;
  2368.     /** 
  2369.      * Configure an item that was installed or upgraded by another process
  2370.      * so that |_finishOperations| can properly complete processing and 
  2371.      * registration. 
  2372.      * As this is the only point at which we can reliably know the Install
  2373.      * Location of this item, we use this as an opportunity to:
  2374.      * 1. Check that this item is compatible with this Firefox version.
  2375.      * 2. If it is, configure the item by using the supplied callback.
  2376.      *    We do not do any special handling in the case that the item is
  2377.      *    not compatible with this version other than to simply not register
  2378.      *    it and log that fact - there is no "phone home" check for updates. 
  2379.      *    It may or may not make sense to do this, but for now we'll just
  2380.      *    not register.
  2381.      * @param   id
  2382.      *          The GUID of the item to validate and configure.
  2383.      * @param   location
  2384.      *          The Install Location where this item is installed.
  2385.      * @param   callback
  2386.      *          The callback that configures the item for installation upon
  2387.      *          successful validation.
  2388.      */      
  2389.     function installItem(id, location, callback) {
  2390.       // As this is the only pint at which we reliably know the installation
  2391.       var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  2392.       if (installRDF.exists()) {
  2393.         LOG("Item Installed/Upgraded at Install Location: " + location.name + 
  2394.             " Item ID: " + id + ", attempting to register...");
  2395.         var installManifest = getInstallManifest(installRDF);
  2396.         var installData = em._getInstallData(installManifest);
  2397.         if (installData.error == INSTALLERROR_SUCCESS) {
  2398.           LOG("... success, item is compatible");
  2399.           callback(installManifest, installData.id, location, installData.type);
  2400.         }
  2401.         else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) {
  2402.           LOG("... success, item installed but is not compatible");
  2403.           callback(installManifest, installData.id, location, installData.type);
  2404.           em._appDisableItem(id);
  2405.         }
  2406.         else {
  2407.           /**
  2408.            * Turns an error code into a message for logging
  2409.            * @param   error
  2410.            *          an Install Error code
  2411.            * @returns A string message to be logged.
  2412.            */
  2413.           function translateErrorMessage(error) {
  2414.             switch (error) {
  2415.             case INSTALLERROR_INVALID_GUID:
  2416.               return "Invalid GUID";
  2417.             case INSTALLERROR_INVALID_VERSION:
  2418.               return "Invalid Version";
  2419.             case INSTALLERROR_INCOMPATIBLE_VERSION:
  2420.               return "Incompatible Version";
  2421.             case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  2422.               return "Incompatible Platform";
  2423.             }
  2424.           }
  2425.           LOG("... failure, item is not compatible, error: " + 
  2426.               translateErrorMessage(installData.error));
  2427.  
  2428.           // Add the item to the Startup Cache anyway, so we don't re-detect it
  2429.           // every time the app starts.
  2430.           StartupCache.put(location, id, OP_NONE, true);
  2431.         }
  2432.       }      
  2433.     }
  2434.   
  2435.     /**
  2436.      * Determines if an item can be used based on whether or not the install
  2437.      * location of the "item" has an equal or higher priority than the install
  2438.      * location where another version may live.
  2439.      * @param   id
  2440.      *          The GUID of the item being installed.
  2441.      * @param   location
  2442.      *          The location where an item is to be installed.
  2443.      * @returns true if the item can be installed at that location, false 
  2444.      *          otherwise.
  2445.      */
  2446.     function canUse(id, location) {
  2447.       for (var locationKey in StartupCache.entries) {
  2448.         if (locationKey != location.name && 
  2449.             id in StartupCache.entries[locationKey]) {
  2450.           if (StartupCache.entries[locationKey][id]) {
  2451.             var oldInstallLocation = InstallLocations.get(locationKey);
  2452.             if (oldInstallLocation.priority <= location.priority)
  2453.               return false;
  2454.           }
  2455.         }
  2456.       }
  2457.       return true;
  2458.     }
  2459.     
  2460.     /** 
  2461.       * Gets a Dialog Param Block loaded with a set of strings to initialize the
  2462.       * XPInstall Confirmation Dialog.
  2463.       * @param   strings
  2464.       *          An array of strings
  2465.       * @returns A nsIDialogParamBlock loaded with the strings and dialog state.
  2466.       */
  2467.     function getParamBlock(strings) {
  2468.       var dpb = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  2469.                           .createInstance(Components.interfaces.nsIDialogParamBlock);
  2470.       // OK and Cancel Buttons
  2471.       dpb.SetInt(0, 2);
  2472.       // Number of Strings
  2473.       dpb.SetInt(1, strings.length);
  2474.       dpb.SetNumberStrings(strings.length);
  2475.       // Add Strings
  2476.       for (var i = 0; i < strings.length; ++i)
  2477.         dpb.SetString(i, strings[i]);
  2478.       
  2479.       var supportsString = Components.classes["@mozilla.org/supports-string;1"]
  2480.                                      .createInstance(Components.interfaces.nsISupportsString);
  2481.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  2482.       supportsString.data = bundle.GetStringFromName("droppedInWarning");
  2483.       var objs = Components.classes["@mozilla.org/array;1"]
  2484.                            .createInstance(Components.interfaces.nsIMutableArray);
  2485.       objs.appendElement(supportsString, false);
  2486.       dpb.objects = objs;
  2487.       return dpb;        
  2488.     }
  2489.  
  2490.     /**
  2491.      * Installs a set of files which were dropped into an install location by 
  2492.      * the user, only after user confirmation.
  2493.      * @param   droppedInFiles
  2494.      *          An array of JS objects with the following properties:
  2495.      *          "file"      The nsILocalFile where the XPI lives
  2496.      *          "location"  The Install Location where the XPI was found. 
  2497.      * @param   xpinstallStrings
  2498.      *          An array of strings used to initialize the XPInstall Confirm 
  2499.      *          dialog.
  2500.      */ 
  2501.     function installDroppedInFiles(droppedInFiles, xpinstallStrings) {
  2502.       if (droppedInFiles.length == 0) 
  2503.         return;
  2504.         
  2505.       var dpb = getParamBlock(xpinstallStrings);
  2506.       var ifptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
  2507.                             .createInstance(Components.interfaces.nsISupportsInterfacePointer);
  2508.       ifptr.data = dpb;
  2509.       ifptr.dataIID = Components.interfaces.nsIDialogParamBlock;
  2510.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  2511.                           .getService(Components.interfaces.nsIWindowWatcher);
  2512.       ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG, 
  2513.                     "", "chrome,centerscreen,modal,dialog,titlebar", ifptr);
  2514.       if (!dpb.GetInt(0)) {
  2515.         // User said OK - install items
  2516.         for (var i = 0; i < droppedInFiles.length; ++i) {
  2517.           em.installItemFromFile(droppedInFiles[i].file, 
  2518.                                  droppedInFiles[i].location.name);
  2519.           // We are responsible for cleaning up this file
  2520.           droppedInFiles[i].file.remove(false);
  2521.         }
  2522.       }
  2523.       else {
  2524.         for (i = 0; i < droppedInFiles.length; ++i) {
  2525.           // We are responsible for cleaning up this file
  2526.           droppedInFiles[i].file.remove(false);
  2527.         }
  2528.       }
  2529.     }
  2530.     
  2531.     var isDirty = false;
  2532.     var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES,
  2533.                                      false);
  2534.     StartupCache.read();
  2535.     
  2536.     // Array of objects with 'location' and 'id' properties to maybe install.
  2537.     var newItems = [];
  2538.  
  2539.     var droppedInFiles = [];
  2540.     var xpinstallStrings = [];
  2541.     
  2542.     // Enumerate over the install locations from low to high priority.  The
  2543.     // enumeration returned is pre-sorted.
  2544.     var installLocations = this.installLocations;
  2545.     while (installLocations.hasMoreElements()) {
  2546.       var location = installLocations.getNext().QueryInterface(nsIInstallLocation);
  2547.  
  2548.       // Hash the set of items actually held by the Install Location.  
  2549.       var actualItems = { };
  2550.       var entries = location.itemLocations;
  2551.       while (true) {
  2552.         var entry = entries.nextFile;
  2553.         if (!entry)
  2554.           break;
  2555.  
  2556.         // Is this location a valid item? It must be a directory, and contain
  2557.         // an install.rdf manifest:
  2558.         if (entry.isDirectory()) {
  2559.           var installRDF = entry.clone();
  2560.           installRDF.append(FILE_INSTALL_MANIFEST);
  2561.  
  2562.           var id = location.getIDForLocation(entry);
  2563.           if (!id || (!installRDF.exists() && 
  2564.                       !location.itemIsManagedIndependently(id)))
  2565.             continue;
  2566.  
  2567.           actualItems[id] = entry;
  2568.         }
  2569.         else {
  2570.           // Check to see if this file is a XPI/JAR dropped into this dir
  2571.           // by the user, installing it if necessary. We do this here rather
  2572.           // than separately in |_finishOperations| because I don't want to
  2573.           // walk these lists multiple times on every startup.
  2574.           var item = this._getItemForDroppedFile(entry, location);
  2575.           if (item) {
  2576.             droppedInFiles.push({ file: entry, location: location });
  2577.  
  2578.             var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  2579.                                       .createInstance(Components.interfaces.nsIZipReader);
  2580.             zipReader.init(entry);
  2581.             var prettyName = "";
  2582.             try {
  2583.               var jar = zipReader.QueryInterface(Components.interfaces.nsIJAR);
  2584.               var principal = { };
  2585.               var certPrincipal = zipReader.getCertificatePrincipal(null, principal);
  2586.               // XXXbz This string could be empty.  This needs better
  2587.               // UI to present principal.value.certificate's subject.
  2588.               prettyName = principal.value.prettyName;
  2589.             }
  2590.             catch (e) { }
  2591.             xpinstallStrings = xpinstallStrings.concat([item.name, 
  2592.                                                         getURLSpecFromFile(entry),
  2593.                                                         item.iconURL, 
  2594.                                                         prettyName]);
  2595.             isDirty = true;
  2596.           }
  2597.         }
  2598.       }
  2599.       
  2600.       if (location.name in StartupCache.entries) {
  2601.         // Look for items that have been uninstalled by removing their directory.
  2602.         for (var id in StartupCache.entries[location.name]) {
  2603.           if (!StartupCache.entries[location.name] ||
  2604.               !StartupCache.entries[location.name][id]) 
  2605.             continue;
  2606.  
  2607.           // Force _finishOperations to run if we have enabled or disabled items.
  2608.           // XXXdarin this should be unnecessary now that we check
  2609.           // PendingOperations.size in start()
  2610.           if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE ||
  2611.               StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE)
  2612.             isDirty = true;
  2613.           
  2614.           if (!(id in actualItems) && 
  2615.               StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL &&
  2616.               StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) {
  2617.             // We have an entry for this id in the Extensions database, for this 
  2618.             // install location, but it no longer exists in the Install Location. 
  2619.             // We can infer from this that the item has been removed, so uninstall
  2620.             // it properly. 
  2621.             if (canUse(id, location)) {
  2622.               LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor + 
  2623.                   " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item.");
  2624.               
  2625.               // Load the Extensions Datasource and force this item into the visible
  2626.               // items list if it is not already. This allows us to handle the case 
  2627.               // where there is an entry for an item in the Startup Cache but not
  2628.               // in the extensions.rdf file - in that case the item will not be in
  2629.               // the visible list and calls to |getInstallLocation| will mysteriously
  2630.               // fail.
  2631.               this.datasource.updateVisibleList(id, location.name, false);
  2632.               this.uninstallItem(id);
  2633.               isDirty = true;
  2634.             }
  2635.           }
  2636.           else if (!ignoreMTimeChanges) {
  2637.             // Look for items whose mtime has changed, and as such we can assume 
  2638.             // they have been "upgraded".
  2639.             var lf = { path: StartupCache.entries[location.name][id].descriptor };
  2640.             try {
  2641.                lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location);
  2642.             }
  2643.             catch (e) { }
  2644.  
  2645.             if (lf.exists && lf.exists()) {
  2646.               var actualMTime = Math.floor(lf.lastModifiedTime / 1000);
  2647.               if (actualMTime != StartupCache.entries[location.name][id].mtime) {
  2648.                 LOG("Item Location path changed: " + lf.path + " Item ID: " + 
  2649.                     id + " Location Key: " + location.name + ", attempting to upgrade item...");
  2650.                 if (canUse(id, location)) {
  2651.                   installItem(id, location, 
  2652.                               function(installManifest, id, location, type) {
  2653.                                 em._upgradeItem(installManifest, id, location, 
  2654.                                                 type);
  2655.                               });
  2656.                   isDirty = true;
  2657.                 }
  2658.               }
  2659.             }
  2660.             else {
  2661.               isDirty = true;
  2662.               LOG("Install Location returned a missing or malformed item path! " + 
  2663.                   "Item Path: " + lf.path + ", Location Key: " + location.name + 
  2664.                   " Item ID: " + id);
  2665.               if (canUse(id, location)) {
  2666.                 // Load the Extensions Datasource and force this item into the visible
  2667.                 // items list if it is not already. This allows us to handle the case 
  2668.                 // where there is an entry for an item in the Startup Cache but not
  2669.                 // in the extensions.rdf file - in that case the item will not be in
  2670.                 // the visible list and calls to |getInstallLocation| will mysteriously
  2671.                 // fail.
  2672.                 this.datasource.updateVisibleList(id, location.name, false);
  2673.                 this.uninstallItem(id);
  2674.               }
  2675.             }
  2676.           }
  2677.         }
  2678.       }
  2679.  
  2680.       // Look for items that have been installed by appearing in the location.
  2681.       for (var id in actualItems) {
  2682.         if (!(location.name in StartupCache.entries) || 
  2683.             !(id in StartupCache.entries[location.name]) ||
  2684.             !StartupCache.entries[location.name][id]) {
  2685.           // Remember that we've seen this item
  2686.           StartupCache.put(location, id, OP_NONE, true);
  2687.           // Push it on the stack of items to maybe install later
  2688.           newItems.push({location: location, id: id});
  2689.         }
  2690.       }
  2691.     }
  2692.  
  2693.     // Process any newly discovered items.  We do this here instead of in the
  2694.     // previous loop so that we can be sure that we have a fully populated
  2695.     // StartupCache.
  2696.     for (var i = 0; i < newItems.length; ++i) {
  2697.       var id = newItems[i].id;
  2698.       var location = newItems[i].location;
  2699.       if (canUse(id, location)) {
  2700.         LOG("Item Installed via directory addition to Install Location: " + 
  2701.             location.name + " Item ID: " + id + ", attempting to register...");
  2702.         installItem(id, location, 
  2703.                     function(installManifest, id, location, type) { 
  2704.                       em._configureForthcomingItem(installManifest, id, location, 
  2705.                                                    type);
  2706.                     });
  2707.         isDirty = true;
  2708.       }
  2709.     }
  2710.  
  2711.     // Ask the user if they want to install the dropped items, for security
  2712.     // purposes.
  2713.     installDroppedInFiles(droppedInFiles, xpinstallStrings);
  2714.     
  2715.     return isDirty;
  2716.   },
  2717.   
  2718.   /**
  2719.    * Upgrades contents.rdf files to chrome.manifest files for any existing
  2720.    * Extensions and Themes.
  2721.    * @returns true if actions were performed that require a restart, false 
  2722.    *          otherwise.
  2723.    */
  2724.   _upgradeChrome: function() {
  2725.     if (inSafeMode())
  2726.       return false;
  2727.  
  2728.     var checkForNewChrome = false;
  2729.     var ds = this.datasource;
  2730.     // If we have extensions that were installed before the new flat chrome
  2731.     // manifests, and are still valid, we need to manually create the flat
  2732.     // manifest files.
  2733.     var extensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION);
  2734.     for (var i = 0; i < extensions.length; ++i) {
  2735.       var e = extensions[i];
  2736.       var itemLocation = e.location.getItemLocation(e.id);
  2737.       var manifest = itemLocation.clone();
  2738.       manifest.append(FILE_CHROME_MANIFEST);
  2739.       if (!manifest.exists()) {
  2740.         var installRDF = itemLocation.clone();
  2741.         installRDF.append(FILE_INSTALL_MANIFEST);
  2742.         var installLocation = this.getInstallLocation(e.id);
  2743.         if (installLocation && installRDF.exists()) {
  2744.           var itemLocation = installLocation.getItemLocation(e.id);
  2745.           if (itemLocation.exists() && itemLocation.isDirectory()) {
  2746.             var installer = new Installer(ds, e.id, installLocation, 
  2747.                                           nsIUpdateItem.TYPE_EXTENSION);
  2748.             installer.upgradeExtensionChrome();
  2749.           }
  2750.         }
  2751.         else {
  2752.           ds.removeItemMetadata(e.id);
  2753.           ds.removeItemFromContainer(e.id);
  2754.         }
  2755.  
  2756.         checkForNewChrome = true;
  2757.       }
  2758.     }
  2759.  
  2760.     var themes = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  2761.     // If we have themes that were installed before the new flat chrome
  2762.     // manifests, and are still valid, we need to manually create the flat
  2763.     // manifest files.
  2764.     for (i = 0; i < themes.length; ++i) {
  2765.       var item = themes[i];
  2766.       var itemLocation = item.location.getItemLocation(item.id);
  2767.       var manifest = itemLocation.clone();
  2768.       manifest.append(FILE_CHROME_MANIFEST);
  2769.       if (manifest.exists() ||
  2770.           item.id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  2771.         continue;
  2772.  
  2773.       var entries;
  2774.       try {
  2775.         var manifestURI = getURIFromFile(manifest);
  2776.         var chromeDir = itemLocation.clone();
  2777.         chromeDir.append(DIR_CHROME);
  2778.         
  2779.         if (!chromeDir.exists() || !chromeDir.isDirectory()) {
  2780.           ds.removeItemMetadata(item.id);
  2781.           ds.removeItemFromContainer(item.id);
  2782.           continue;
  2783.         }
  2784.  
  2785.         // We're relying on the fact that there is only one JAR file
  2786.         // in the "chrome" directory. This is a hack, but it works.
  2787.         entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  2788.         var jarFile = entries.nextFile;
  2789.         if (jarFile) {
  2790.           var jarFileURI = getURIFromFile(jarFile);
  2791.           var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  2792.  
  2793.           // Use the Chrome Registry API to install the theme there
  2794.           var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  2795.                             .getService(Components.interfaces.nsIToolkitChromeRegistry);
  2796.           cr.processContentsManifest(contentsURI, manifestURI, contentsURI, false, true);
  2797.         }
  2798.         entries.close();
  2799.       }
  2800.       catch (e) {
  2801.         LOG("_upgradeChrome: failed to upgrade contents manifest for " + 
  2802.             "theme: " + item.id + ", exception: " + e + "... The theme will be " + 
  2803.             "disabled.");
  2804.         this._appDisableItem(item.id);
  2805.       }
  2806.       finally {
  2807.         try {
  2808.           entries.close();
  2809.         }
  2810.         catch (e) {
  2811.         }
  2812.       }
  2813.       checkForNewChrome = true;
  2814.     }
  2815.     return checkForNewChrome;  
  2816.   },
  2817.   
  2818.   _checkForUncoveredItem: function(id) {
  2819.     var ds = this.datasource;
  2820.     var oldLocation = this.getInstallLocation(id);
  2821.     var newLocations = [];
  2822.     for (var locationKey in StartupCache.entries) {
  2823.       var location = InstallLocations.get(locationKey);
  2824.       if (id in StartupCache.entries[locationKey] && 
  2825.           location.priority > oldLocation.priority)
  2826.         newLocations.push(location);
  2827.     }
  2828.     newLocations.sort(function(a, b) { return b.priority - a.priority; });
  2829.     if (newLocations.length > 0) {
  2830.       for (var i = 0; i < newLocations.length; ++i) {
  2831.         // Check to see that the item at the location exists
  2832.         var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST);
  2833.         if (installRDF.exists()) {
  2834.           // Update the visible item cache so that |_finalizeUpgrade| is properly 
  2835.           // called from |_finishOperations|
  2836.           var name = newLocations[i].name;
  2837.           ds.updateVisibleList(id, name, true);
  2838.           PendingOperations.addItem(OP_NEEDS_UPGRADE, 
  2839.                                     { locationKey: name, id: id });
  2840.           PendingOperations.addItem(OP_NEEDS_INSTALL, 
  2841.                                     { locationKey: name, id: id });
  2842.           break;
  2843.         }
  2844.         else {
  2845.           // If no item exists at the location specified, remove this item
  2846.           // from the visible items list and check again. 
  2847.           StartupCache.clearEntry(newLocations[i], id);
  2848.           ds.updateVisibleList(id, null, true);
  2849.         }
  2850.       }
  2851.     }
  2852.     else
  2853.       ds.updateVisibleList(id, null, true);
  2854.   },
  2855.   
  2856.   /**
  2857.    * Finish up pending operations - perform upgrades, installs, enables/disables, 
  2858.    * uninstalls etc.
  2859.    * @returns true if actions were performed that require a restart, false 
  2860.    *          otherwise.
  2861.    */
  2862.   _finishOperations: function() {
  2863.     try {
  2864.       // Stuff has changed, load the Extensions datasource in all its RDFey
  2865.       // glory. 
  2866.       var ds = this.datasource;
  2867.       var updatedTargetAppInfos = [];
  2868.  
  2869.       var needsRestart = false;      
  2870.       do {
  2871.         // Enable and disable during startup so items that are changed in the
  2872.         // ui can be reset to a no-op.
  2873.         // Look for extensions that need to be enabled.
  2874.         var items = PendingOperations.getOperations(OP_NEEDS_ENABLE);
  2875.         for (var i = items.length - 1; i >= 0; --i) {
  2876.           var id = items[0].id;
  2877.           ds.setItemProperty(id, EM_R("userDisabled"), null);
  2878.           ds.setItemProperty(id, EM_R("appDisabled"), null);
  2879.           var installLocation = this.getInstallLocation(id);
  2880.           StartupCache.put(installLocation, id, OP_NONE, true);
  2881.           PendingOperations.clearItem(OP_NEEDS_ENABLE, id);
  2882.           needsRestart = true;
  2883.         }
  2884.         PendingOperations.clearItems(OP_NEEDS_ENABLE);
  2885.  
  2886.         // Look for extensions that need to be disabled.
  2887.         items = PendingOperations.getOperations(OP_NEEDS_DISABLE);
  2888.         for (i = items.length - 1; i >= 0; --i) {
  2889.           id = items[i].id;
  2890.           // Only set the userDisabled property when the item's appDisabled
  2891.           // property is not true since an item can't be disabled by the user
  2892.           // when it is already disabled by the application.
  2893.           if (ds.getItemProperty(id, "appDisabled") != "true")
  2894.             ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  2895.           installLocation = this.getInstallLocation(id);
  2896.           StartupCache.put(installLocation, id, OP_NONE, true);
  2897.           PendingOperations.clearItem(OP_NEEDS_DISABLE, id);
  2898.         }
  2899.         PendingOperations.clearItems(OP_NEEDS_DISABLE);
  2900.  
  2901.         // Look for extensions that need to be upgraded. The process here is to
  2902.         // uninstall the old version of the extension first, then install the
  2903.         // new version in its place. 
  2904.         items = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  2905.         for (i = items.length - 1; i >= 0; --i) {
  2906.           id = items[i].id;
  2907.           var oldLocation = this.getInstallLocation(id);
  2908.           var newLocation = InstallLocations.get(items[i].locationKey);
  2909.           if (newLocation.priority <= oldLocation.priority) {
  2910.             // check if there is updated app compatibility info
  2911.             var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  2912.             if (newTargetAppInfo)
  2913.               updatedTargetAppInfos.push(newTargetAppInfo);
  2914.             this._finalizeUpgrade(id);
  2915.           }
  2916.         }
  2917.         PendingOperations.clearItems(OP_NEEDS_UPGRADE);
  2918.  
  2919.         // Install items
  2920.         items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  2921.         for (i = items.length - 1; i >= 0; --i) {
  2922.           needsRestart = true;
  2923.           id = items[i].id;
  2924.           // check if there is updated app compatibility info
  2925.           newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  2926.           if (newTargetAppInfo)
  2927.             updatedTargetAppInfos.push(newTargetAppInfo);
  2928.           this._finalizeInstall(id, null);
  2929.         }
  2930.         PendingOperations.clearItems(OP_NEEDS_INSTALL);
  2931.  
  2932.         // Look for extensions that need to be removed. This MUST be done after
  2933.         // the install operations since extensions to be installed may have to be
  2934.         // uninstalled if there are errors during the installation process!
  2935.         items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL);
  2936.         for (i = items.length - 1; i >= 0; --i) {
  2937.           id = items[i].id;
  2938.           this._finalizeUninstall(id);
  2939.           this._checkForUncoveredItem(id);
  2940.           needsRestart = true;
  2941.         }
  2942.         PendingOperations.clearItems(OP_NEEDS_UNINSTALL);
  2943.       }
  2944.       while (PendingOperations.size > 0);
  2945.       
  2946.       // Upgrade contents.rdf files to the new chrome.manifest format for
  2947.       // existing Extensions and Themes
  2948.       if (this._upgradeChrome()) {
  2949.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  2950.                            .getService(Components.interfaces.nsIChromeRegistry);
  2951.         cr.checkForNewChrome();
  2952.       }      
  2953.  
  2954.       // If no additional restart is required, it implies that there are
  2955.       // no new components that need registering so we can inform the app
  2956.       // not to do any extra startup checking next time round. 
  2957.       this._updateManifests(needsRestart);
  2958.  
  2959.       // If there is updated app compatibility info update the data sources.
  2960.       for (i = 0; i < updatedTargetAppInfos.length; ++i) {
  2961.         ds.updateTargetAppInfo(updatedTargetAppInfos[i].id,
  2962.                                updatedTargetAppInfos[i].minVersion,
  2963.                                updatedTargetAppInfos[i].maxVersion);
  2964.       }
  2965.     }
  2966.     catch (e) {
  2967.       LOG("ExtensionManager:_finishOperations - failure, catching exception - lineno: " +
  2968.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  2969.     }
  2970.     return needsRestart;
  2971.   },
  2972.   
  2973.   /**
  2974.    * Checks to see if there are items that are incompatible with this version
  2975.    * of the application, disables them to prevent incompatibility problems and 
  2976.    * invokes the Update Wizard to look for newer versions.
  2977.    * @returns true if there were incompatible items installed and disabled, and
  2978.    *          the application must now be restarted to reinitialize XPCOM,
  2979.    *          false otherwise.
  2980.    */
  2981.   checkForMismatches: function() {
  2982.     // Check to see if the version of the application that is being started
  2983.     // now is the same one that was started last time. 
  2984.     var currAppVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  2985.                                  gApp.version);
  2986.     var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, "");
  2987.     if (currAppVersion == lastAppVersion)
  2988.       return false;
  2989.     // With a new profile lastAppVersion doesn't exist yet.
  2990.     if (!lastAppVersion) {
  2991.       gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  2992.       return false;
  2993.     }
  2994.  
  2995.     // Version mismatch, we have to load the extensions datasource and do
  2996.     // version checking. Time hit here doesn't matter since this doesn't happen
  2997.     // all that often.
  2998.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  2999.     if (!extensionsDS.exists())
  3000.       this._upgradeFromV10();
  3001.     
  3002.     // Make the extensions datasource consistent if it isn't already.
  3003.     var isDirty = false;
  3004.     if (this._ensureDatasetIntegrity())
  3005.       isDirty = true;
  3006.  
  3007.     if (this._checkForFileChanges())
  3008.       isDirty = true;
  3009.  
  3010.     if (PendingOperations.size != 0)
  3011.       isDirty = true;
  3012.  
  3013.     if (isDirty)
  3014.       this._finishOperations();
  3015.  
  3016.     // Disable all incompatible items and let update enable them if appropriate.
  3017.     var ds = this.datasource;
  3018.     var currAppID = gApp.ID;
  3019.     var items = ds.getIncompatibleItemList(currAppID, currAppVersion,
  3020.                                            nsIUpdateItem.TYPE_ADDON, true);
  3021.     for (var i = 0; i < items.length; ++i)
  3022.       ds.setItemProperty(items[i].id, EM_R("appDisabled"), EM_L("true"));
  3023.     // Update the manifests to reflect the items that were disabled.
  3024.     this._updateManifests(true);
  3025.  
  3026.     // Always check for compatibility updates when upgrading
  3027.     this._showMismatchWindow();
  3028.     
  3029.     // Finish any pending upgrades from the compatibility update to avoid an
  3030.     // additional restart.
  3031.     if (PendingOperations.size != 0)
  3032.       this._finishOperations();
  3033.  
  3034.     // Update the last app version so we don't do this again with this version.
  3035.     gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3036.  
  3037.     return true;
  3038.   },
  3039.  
  3040.   /**
  3041.    * Shows the "Compatibility Updates" UI
  3042.    */
  3043.   _showMismatchWindow: function(items) {
  3044.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3045.                        .getService(Components.interfaces.nsIWindowMediator);
  3046.     var wizard = wm.getMostRecentWindow("Update:Wizard");
  3047.     if (wizard)
  3048.       wizard.focus();
  3049.     else {
  3050.       var features = "chrome,centerscreen,dialog,titlebar,modal";
  3051.       // This *must* be modal so as not to break startup! This code is invoked before
  3052.       // the main event loop is initiated (via checkForMismatches).
  3053.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3054.                          .getService(Components.interfaces.nsIWindowWatcher);
  3055.       ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, null);
  3056.     }
  3057.   },
  3058.   
  3059.   /*
  3060.    * Catch all for facilitating a version 1.0 profile upgrade.
  3061.    * 1) removes the abandoned default theme directory from the profile.
  3062.    * 2) prepares themes installed with version 1.0 for installation.
  3063.    * 3) initiates an install to populate the new extensions datasource.
  3064.    * 4) migrates the disabled attribute from the old datasource.
  3065.    * 5) migrates the app compatibility info from the old datasource.
  3066.    */
  3067.   _upgradeFromV10: function() {
  3068.     // return early if the version 1.0 extensions datasource file doesn't exist.
  3069.     var oldExtensionsFile = getFile(KEY_PROFILEDIR, [DIR_EXTENSIONS, "Extensions.rdf"]);
  3070.     if (!oldExtensionsFile.exists())
  3071.       return;
  3072.  
  3073.     // Version 1.0 profiles have a default theme directory in the profile's
  3074.     // extensions directory that will be disabled due to having a maxVersion
  3075.     // of 1.0 so we must remove it if it exists.
  3076.     var profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3077.                                              stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)]);
  3078.     if (profileDefaultTheme && profileDefaultTheme.exists())
  3079.       profileDefaultTheme.remove(true);
  3080.  
  3081.     // Version 0.9 profiles may have DOMi 1.0 with just an install.rdf
  3082.     var profileDOMi = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3083.                                      "{641d8d09-7dda-4850-8228-ac0ab65e2ac9}"]);
  3084.     if (profileDOMi && profileDOMi.exists())
  3085.       profileDOMi.remove(true);
  3086.  
  3087.     // Prepare themes for installation
  3088.     // Only enumerate directories in the app-profile and app-global locations.
  3089.     var locations = [KEY_APP_PROFILE, KEY_APP_GLOBAL];
  3090.     for (var i = 0; i < locations.length; ++i) {
  3091.       var location = InstallLocations.get(locations[i]);
  3092.       if (!location.canAccess)
  3093.         continue;
  3094.  
  3095.       var entries = location.itemLocations;
  3096.       var entry;
  3097.       while ((entry = entries.nextFile)) {
  3098.         var installRDF = entry.clone();
  3099.         installRDF.append(FILE_INSTALL_MANIFEST);
  3100.  
  3101.         var chromeDir = entry.clone();
  3102.         chromeDir.append(DIR_CHROME);
  3103.  
  3104.         // It must be a directory without an install.rdf and it must contain
  3105.         // a chrome directory
  3106.         if (!entry.isDirectory() || installRDF.exists() || !chromeDir.exists())
  3107.           continue;
  3108.  
  3109.         var chromeEntries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  3110.         if (!chromeEntries.hasMoreElements())
  3111.           continue;
  3112.  
  3113.         // We're relying on the fact that there is only one JAR file
  3114.         // in the "chrome" directory. This is a hack, but it works.
  3115.         var jarFile = chromeEntries.nextFile;
  3116.         var id = location.getIDForLocation(entry);
  3117.  
  3118.         try {
  3119.           var zipReader = getZipReaderForFile(jarFile);
  3120.           zipReader.extract(FILE_INSTALL_MANIFEST, installRDF);
  3121.  
  3122.           var contentsManifestFile = location.getItemFile(id, FILE_CONTENTS_MANIFEST);
  3123.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  3124.  
  3125.           var rootFiles = ["preview.png", "icon.png"];
  3126.           for (var i = 0; i < rootFiles.length; ++i) {
  3127.             try {
  3128.               var target = location.getItemFile(id, rootFiles[i]);
  3129.               zipReader.extract(rootFiles[i], target);
  3130.             }
  3131.             catch (e) {
  3132.             }
  3133.           }
  3134.         }
  3135.         catch (e) {
  3136.           LOG("ExtensionManager:_upgradeFromV10 - failed to extract theme files\r\n" +
  3137.               "Exception: " + e);
  3138.         }
  3139.         zipReader.close();
  3140.       }
  3141.     }
  3142.  
  3143.     // When upgrading from a version 1.0 profile we need to populate the
  3144.     // extensions datasource with all items before checking for incompatible
  3145.     // items since the datasource hasn't been created yet.
  3146.     var itemsToCheck = [];
  3147.     if (this._checkForFileChanges()) {
  3148.       // Create a list of all items that are to be installed so we can migrate
  3149.       // these items's settings to the new datasource.
  3150.       var items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3151.       for (i = items.length - 1; i >= 0; --i) {
  3152.         if (items[i].locationKey == KEY_APP_PROFILE ||
  3153.             items[i].locationKey == KEY_APP_GLOBAL)
  3154.           itemsToCheck.push(items[i].id);
  3155.       }
  3156.       this._finishOperations();
  3157.     }
  3158.  
  3159.     // If there are no items to migrate settings for return early.
  3160.     if (itemsToCheck.length == 0)
  3161.       return;
  3162.  
  3163.     var fileURL = getURLSpecFromFile(oldExtensionsFile);
  3164.     var oldExtensionsDS = gRDF.GetDataSourceBlocking(fileURL);
  3165.     var versionChecker = getVersionChecker();
  3166.     var ds = this.datasource;
  3167.     var currAppVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  3168.                                  gApp.version);
  3169.     var currAppID = gApp.ID;
  3170.     for (var i = 0; i < itemsToCheck.length; ++i) {
  3171.       var item = ds.getItemForID(itemsToCheck[i]);
  3172.       var oldPrefix = (item.type == nsIUpdateItem.TYPE_EXTENSION) ? PREFIX_EXTENSION : PREFIX_THEME;
  3173.       var oldRes = gRDF.GetResource(oldPrefix + item.id);
  3174.       // Disable the item if it was disabled in the version 1.0 extensions
  3175.       // datasource.
  3176.       if (oldExtensionsDS.GetTarget(oldRes, EM_R("disabled"), true))
  3177.         ds.setItemProperty(item.id, EM_R("userDisabled"), EM_L("true"));
  3178.  
  3179.       // app enable all items. If it is incompatible it will be app disabled
  3180.       // later on.
  3181.       ds.setItemProperty(item.id, EM_R("appDisabled"), null);
  3182.  
  3183.       // if the item is already compatible don't attempt to migrate the
  3184.       // item's compatibility info
  3185.       var newRes = getResourceForID(itemsToCheck[i]);
  3186.       if (ds.isCompatible(ds, newRes))
  3187.         continue;
  3188.  
  3189.       var updatedMinVersion = null;
  3190.       var updatedMaxVersion = null;
  3191.       var targetApps = oldExtensionsDS.GetTargets(oldRes, EM_R("targetApplication"), true);
  3192.       while (targetApps.hasMoreElements()) {
  3193.         var targetApp = targetApps.getNext();
  3194.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  3195.           try {
  3196.             var foundAppID = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("id"), true));
  3197.             if (foundAppID != currAppID) // Different target application
  3198.               continue;
  3199.  
  3200.             updatedMinVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("minVersion"), true));
  3201.             updatedMaxVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("maxVersion"), true));
  3202.  
  3203.             // Only set the target app info if the extension's target app info
  3204.             // in the version 1.0 extensions datasource makes it compatible
  3205.             if (versionChecker.compare(currAppVersion, updatedMinVersion) >= 0 &&
  3206.                 versionChecker.compare(currAppVersion, updatedMaxVersion) <= 0)
  3207.               ds.updateTargetAppInfo(item.id, updatedMinVersion, updatedMaxVersion);
  3208.  
  3209.             break;
  3210.           }
  3211.           catch (e) { 
  3212.           }
  3213.         }
  3214.       }
  3215.     }
  3216.   },
  3217.  
  3218.   /**
  3219.    * Write the Extensions List and the Startup Cache
  3220.    * @param   needsRestart
  3221.    *          true if the application needs to restart again, false otherwise.
  3222.    */  
  3223.   _updateManifests: function(needsRestart) {
  3224.     // Write the Startup Cache (All Items, visible or not)
  3225.     StartupCache.write();
  3226.     // Write the Extensions Locations Manifest (Visible, enabled items)
  3227.     this._updateExtensionsManifest(needsRestart);
  3228.   },
  3229.  
  3230.   /**
  3231.    * Get a list of items that are currently "active" (turned on) of a specific
  3232.    * type
  3233.    * @param   type
  3234.    *          The nsIUpdateItem type to return a list of items of
  3235.    * @returns An array of active items of the specified type.
  3236.    */
  3237.   _getActiveItems: function(type) {
  3238.     var allItems = this.getItemList(type, { });
  3239.     var activeItems = [];
  3240.     var ds = this.datasource;
  3241.     for (var i = 0; i < allItems.length; ++i) {
  3242.       var item = allItems[i];
  3243.  
  3244.       // An item entry is valid only if it is not disabled, not about to 
  3245.       // be disabled, and not about to be uninstalled.
  3246.       var installLocation = this.getInstallLocation(item.id);
  3247.       if (installLocation.name in StartupCache.entries &&
  3248.           item.id in StartupCache.entries[installLocation.name] &&
  3249.           StartupCache.entries[installLocation.name][item.id]) {
  3250.         var op = StartupCache.entries[installLocation.name][item.id].op;
  3251.         if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE || 
  3252.             op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE)
  3253.           continue;
  3254.       }
  3255.       // Suppress items that have been disabled by the user or the app.
  3256.       if (ds.getItemProperty(item.id, "disabled") != "true")
  3257.         activeItems.push({ id: item.id, location: installLocation });
  3258.     }
  3259.  
  3260.     return activeItems;
  3261.   },
  3262.   
  3263.   /**
  3264.    * Write the Extensions List
  3265.    * @param   needsRestart
  3266.    *          true if the application needs to restart again, false otherwise.
  3267.    */
  3268.   _updateExtensionsManifest: function(needsRestart) {
  3269.     // When an operation is performed that requires a component re-registration
  3270.     // (extension enabled/disabled, installed, uninstalled), we must write the
  3271.     // set of paths where extensions live so that the startup system can determine
  3272.     // where additional components, preferences, chrome manifests etc live.
  3273.     //
  3274.     // To do this we obtain a list of active extensions and themes and write 
  3275.     // these to the extensions.ini file in the profile directory.
  3276.     var validExtensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION);
  3277.     var validThemes     = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  3278.  
  3279.     var extensionsLocationsFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  3280.     var fos = openSafeFileOutputStream(extensionsLocationsFile);
  3281.         
  3282.     var extensionSectionHeader = "[ExtensionDirs]\r\n";
  3283.     fos.write(extensionSectionHeader, extensionSectionHeader.length);
  3284.     for (i = 0; i < validExtensions.length; ++i) {
  3285.       var e = validExtensions[i];
  3286.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  3287.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3288.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3289.       fos.write(line, line.length);
  3290.     }
  3291.  
  3292.     var themeSectionHeader = "[ThemeDirs]\r\n";
  3293.     fos.write(themeSectionHeader, themeSectionHeader.length);
  3294.     for (i = 0; i < validThemes.length; ++i) {
  3295.       var e = validThemes[i];
  3296.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  3297.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3298.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3299.       fos.write(line, line.length);
  3300.     }
  3301.  
  3302.     closeSafeFileOutputStream(fos);
  3303.  
  3304.     // Now refresh the compatibility manifest.
  3305.     this._extensionListChanged = needsRestart;
  3306.   },
  3307.   
  3308.   /**
  3309.    * Say whether or not the Extension List has changed (and thus whether or not
  3310.    * the system will have to restart the next time it is started).
  3311.    * @param   val
  3312.    *          true if the Extension List has changed, false otherwise.
  3313.    * @returns |val|
  3314.    */
  3315.   set _extensionListChanged(val) {
  3316.     // When an extension has an operation perform on it (e.g. install, upgrade,
  3317.     // disable, etc.) we are responsible for creating the .autoreg file and
  3318.     // nsAppRunner is responsible for removing it on restart. At some point it
  3319.     // may make sense to be able to cancel a registration but for now we only
  3320.     // create the file.
  3321.     try {
  3322.       var autoregFile = getFile(KEY_PROFILEDIR, [FILE_AUTOREG]);
  3323.       if (val && !autoregFile.exists())
  3324.         autoregFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  3325.     }
  3326.     catch (e) {
  3327.     }
  3328.     return val;
  3329.   },
  3330.   
  3331.   /**
  3332.    * Gathers data about an item specified by the supplied Install Manifest
  3333.    * and determines whether or not it can be installed as-is. It makes this 
  3334.    * determination by validating the item's GUID, Version, and determining 
  3335.    * if it is compatible with this application.
  3336.    * @param   installManifest 
  3337.    *          A nsIRDFDataSource representing the Install Manifest of the 
  3338.    *          item to be installed.
  3339.    * @return  A JS Object with the following properties:
  3340.    *          "id"       The GUID of the Item being installed.
  3341.    *          "version"  The Version string of the Item being installed.
  3342.    *          "name"     The Name of the Item being installed.
  3343.    *          "type"     The nsIUpdateItem type of the Item being installed.
  3344.    *          "targetApps" An array of TargetApplication Info Objects
  3345.    *                     with "id", "minVersion" and "maxVersion" properties,
  3346.    *                     representing applications targeted by this item.
  3347.    *          "error"    The result code:
  3348.    *                     INSTALLERROR_SUCCESS      
  3349.    *                       no error, item can be installed
  3350.    *                     INSTALLERROR_INVALID_GUID 
  3351.    *                       error, GUID is not well-formed
  3352.    *                     INSTALLERROR_INVALID_VERSION
  3353.    *                       error, Version is not well-formed
  3354.    *                     INSTALLERROR_INCOMPATIBLE_VERSION
  3355.    *                       error, item is not compatible with this version
  3356.    *                       of the application.
  3357.    *                     INSTALLERROR_INCOMPATIBLE_PLATFORM
  3358.    *                       error, item is not compatible with the operating
  3359.    *                       system or ABI the application was built for.
  3360.    */
  3361.   _getInstallData: function(installManifest) {
  3362.     var installData = { id          : "", 
  3363.                         version     : "", 
  3364.                         name        : "", 
  3365.                         type        : 0, 
  3366.                         error       : INSTALLERROR_SUCCESS, 
  3367.                         targetApps  : [],
  3368.                         currentApp  : null };
  3369.  
  3370.     // Fetch properties from the Install Manifest
  3371.     installData.id       = getManifestProperty(installManifest, "id");
  3372.     installData.version  = getManifestProperty(installManifest, "version");
  3373.     installData.name     = getManifestProperty(installManifest, "name");
  3374.     installData.type     = getAddonTypeFromInstallManifest(installManifest);
  3375.     installData.updateURL= getManifestProperty(installManifest, "updateURL");
  3376.  
  3377.     /**
  3378.      * Reads a property off a Target Application resource
  3379.      * @param   resource
  3380.      *          The RDF Resource for a Target Application
  3381.      * @param   property
  3382.      *          The property (less EM_NS) to read
  3383.      * @returns The string literal value of the property.
  3384.      */
  3385.     function readTAProperty(resource, property) {
  3386.       return stringData(installManifest.GetTarget(resource, EM_R(property), true));
  3387.     }
  3388.     
  3389.     var targetApps = installManifest.GetTargets(gInstallManifestRoot, 
  3390.                                                 EM_R("targetApplication"), 
  3391.                                                 true);
  3392.     while (targetApps.hasMoreElements()) {
  3393.       var targetApp = targetApps.getNext();
  3394.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  3395.         try {
  3396.           var data = { id        : readTAProperty(targetApp, "id"),
  3397.                        minVersion: readTAProperty(targetApp, "minVersion"),
  3398.                        maxVersion: readTAProperty(targetApp, "maxVersion") };
  3399.           installData.targetApps.push(data);
  3400.           if (data.id == gApp.ID) 
  3401.             installData.currentApp = data;
  3402.         }
  3403.         catch (e) {
  3404.           continue;
  3405.         }
  3406.       }
  3407.     }
  3408.  
  3409.     // If the item specifies one or more target platforms, make sure our OS/ABI
  3410.     // combination is in the list - otherwise, refuse to install the item.
  3411.     var targetPlatforms = null;
  3412.     try {
  3413.       targetPlatforms = installManifest.GetTargets(gInstallManifestRoot, 
  3414.                                                    EM_R("targetPlatform"), 
  3415.                                                    true);
  3416.     } catch(e) {
  3417.       // No targetPlatform nodes, continue.
  3418.     }
  3419.     if (targetPlatforms != null && targetPlatforms.hasMoreElements()) {
  3420.       var foundMatchingOS = false;
  3421.       var foundMatchingOSAndABI = false;
  3422.       var requireABICompatibility = false;
  3423.       while (targetPlatforms.hasMoreElements()) {
  3424.         var targetPlatform = stringData(targetPlatforms.getNext());
  3425.         var tokens = targetPlatform.split("_");
  3426.         var os = tokens[0];
  3427.         var abi = (tokens.length > 1) ? tokens[1] : null;
  3428.         if (os == gOSTarget) {
  3429.           foundMatchingOS = true;
  3430.           // The presence of any ABI part after our OS means ABI is important.
  3431.           if (abi != null) {
  3432.             requireABICompatibility = true;
  3433.             // If we don't know our ABI, we can't be compatible
  3434.             if (abi == gXPCOMABI && abi != UNKNOWN_XPCOM_ABI) {
  3435.               foundMatchingOSAndABI = true;
  3436.               break;
  3437.             }
  3438.           }
  3439.         }
  3440.       }
  3441.       if (!foundMatchingOS || (requireABICompatibility && !foundMatchingOSAndABI)) {
  3442.         installData.error = INSTALLERROR_INCOMPATIBLE_PLATFORM;
  3443.         return installData;
  3444.       }
  3445.     }
  3446.  
  3447.     // Validate the Item ID
  3448.     if (!gIDTest.test(installData.id)) {
  3449.       installData.error = INSTALLERROR_INVALID_GUID;
  3450.       return installData;
  3451.     }
  3452.      
  3453.     // Check the target application range specified by the extension metadata.
  3454.     if (!this.datasource.isCompatible(installManifest, gInstallManifestRoot, undefined))
  3455.       installData.error = INSTALLERROR_INCOMPATIBLE_VERSION;
  3456.     
  3457.     return installData;
  3458.   },  
  3459.   
  3460.   /**
  3461.    * Installs an item from a XPI/JAR file. 
  3462.    * This is the main entry point into the Install system from outside code
  3463.    * (e.g. XPInstall).
  3464.    * @param   aXPIFile
  3465.    *          The file to install from.
  3466.    * @param   aInstallLocationKey
  3467.    *          The name of the Install Location where this item should be 
  3468.    *          installed.
  3469.    */  
  3470.   installItemFromFile: function(xpiFile, installLocationKey) {
  3471.     this.installItemFromFileInternal(xpiFile, installLocationKey, null);
  3472.   },
  3473.   
  3474.   /**
  3475.    * Installs an item from a XPI/JAR file.
  3476.    * @param   aXPIFile
  3477.    *          The file to install from.
  3478.    * @param   aInstallLocationKey
  3479.    *          The name of the Install Location where this item should be 
  3480.    *          installed.
  3481.    * @param   aInstallManifest
  3482.    *          An updated Install Manifest from the Version Update check.
  3483.    *          Can be null when invoked from callers other than the Version
  3484.    *          Update check.
  3485.    */
  3486.   installItemFromFileInternal: function(aXPIFile, aInstallLocationKey, aInstallManifest) {
  3487.     var em = this;
  3488.     /**
  3489.      * Gets the Install Location for an Item.
  3490.      * @param   itemID 
  3491.      *          The GUID of the item to find an Install Location for.
  3492.      * @return  An object implementing nsIInstallLocation which represents the 
  3493.      *          location where the specified item should be installed. 
  3494.      *          This can be:
  3495.      *          1. an object that corresponds to the location key supplied to
  3496.      *             |installItemFromFileInternal|,
  3497.      *          2. the default install location (the App Profile Extensions Folder)
  3498.      *             if no location key was supplied, or the location key supplied
  3499.      *             was not in the set of registered locations
  3500.      *          3. null, if the location selected by 1 or 2 above does not support
  3501.      *             installs from XPI/JAR files, or that location is not writable 
  3502.      *             with the current access privileges.
  3503.      */
  3504.     function getInstallLocation(itemID) {
  3505.       // Here I use "upgrade" to mean "install a different version of an item".
  3506.       var installLocation = em.getInstallLocation(itemID);
  3507.       if (!installLocation) {
  3508.         // This is not an "upgrade", since we don't have any location data for the
  3509.         // extension ID specified - that is, it's not in our database.
  3510.  
  3511.         // Caller supplied a key to a registered location, use that location
  3512.         // for the installation
  3513.         installLocation = InstallLocations.get(aInstallLocationKey);
  3514.         if (installLocation) {
  3515.           // If the specified location does not have a common metadata location
  3516.           // (e.g. extensions have no common root, or other location specified
  3517.           // by the location implementation) - e.g. for a Registry Key enumeration
  3518.           // location - we cannot install or upgrade using a XPI file, probably
  3519.           // because these application types will be handling upgrading themselves.
  3520.           // Just bail.
  3521.           if (!installLocation.location) {
  3522.             LOG("Install Location \"" + installLocation.name + "\" does not support " + 
  3523.                 "installation of items from XPI/JAR files. You must manage " + 
  3524.                 "installation and update of these items yourself.");
  3525.             installLocation = null;
  3526.           }
  3527.         }
  3528.         else {
  3529.           // In the absence of a preferred install location, just default to
  3530.           // the App-Profile 
  3531.           installLocation = InstallLocations.get(KEY_APP_PROFILE);
  3532.         }
  3533.       } 
  3534.       else {
  3535.         // This is an "upgrade", but not through the Update System, because the
  3536.         // Update code will not let an extension with an incompatible target
  3537.         // app version range through to this point. This is an "upgrade" in the
  3538.         // sense that the user found a different version of an installed extension
  3539.         // and installed it through the web interface, so we have metadata.
  3540.         
  3541.         // If the location is different, return the preferred location rather than
  3542.         // the location of the currently installed version, because we may be in
  3543.         // the situation where an item is being installed into the global app 
  3544.         // dir when there's a version in the profile dir.
  3545.         if (installLocation.name != aInstallLocationKey) 
  3546.           installLocation = InstallLocations.get(aInstallLocationKey);
  3547.       }
  3548.       if (!installLocation.canAccess) {
  3549.         LOG("Install Location\"" + installLocation.name + "\" cannot be written " +
  3550.             "to with your access privileges. Installation will not proceed.");
  3551.         installLocation = null;
  3552.       }
  3553.       return installLocation;
  3554.     }
  3555.     
  3556.     /**
  3557.      * Stages a XPI file in the default item location specified by other 
  3558.      * applications when they registered with XulRunner if the item's
  3559.      * install manifest specified compatibility with them.
  3560.      */
  3561.     function stageXPIForOtherApps(xpiFile, installData) {
  3562.       for (var i = 0; i < installData.targetApps.length; ++i) {
  3563.         var targetApp = installData.targetApps[i];
  3564.         if (targetApp.id != gApp.ID) {
  3565.         /* XXXben uncomment when this works!
  3566.           var settingsThingy = Components.classes[]
  3567.                                         .getService(Components.interfaces.nsIXULRunnerSettingsThingy);
  3568.           try {
  3569.             var appPrefix = "SOFTWARE\\Mozilla\\XULRunner\\Applications\\";
  3570.             var branch = settingsThingy.getBranch(appPrefix + targetApp.id);
  3571.             var path = branch.getProperty("ExtensionsLocation");
  3572.             var destination = Components.classes["@mozilla.org/file/local;1"]
  3573.                                         .createInstance(nsILocalFile);
  3574.             destination.initWithPath(path);
  3575.             xpiFile.copyTo(file, xpiFile.leafName);
  3576.           }
  3577.           catch (e) {
  3578.           }
  3579.          */
  3580.         } 
  3581.       }        
  3582.     }
  3583.     
  3584.     /**
  3585.      * Extracts and then starts the install for extensions / themes contained
  3586.      * within a xpi.
  3587.      */
  3588.     function installMultiXPI(xpiFile, installData) {
  3589.       var fileURL = getURIFromFile(xpiFile).QueryInterface(nsIURL);
  3590.       if (fileURL.fileExtension.toLowerCase() != "xpi") {
  3591.         LOG("Invalid File Extension: Item: \"" + fileURL.fileName + "\" has an " + 
  3592.             "invalid file extension. Only xpi file extensions are allowed for " +
  3593.             "multiple item packages.");
  3594.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3595.         showMessage("invalidFileExtTitle", [], 
  3596.                     "invalidFileExtMessage", [installData.name,
  3597.                     fileURL.fileExtension,
  3598.                     bundle.GetStringFromName("type-" + installData.type)]);
  3599.         return;
  3600.       }
  3601.  
  3602.       try {
  3603.         var zipReader = getZipReaderForFile(xpiFile);
  3604.       }
  3605.       catch (e) {
  3606.         LOG("installMultiXPI: failed to open xpi file: " + xpiFile.path);
  3607.         throw e;
  3608.       }
  3609.  
  3610.       var searchForEntries = ["*.xpi", "*.jar"];
  3611.       var files = [];
  3612.       for (var i = 0; i < searchForEntries.length; ++i) {
  3613.         var entries = zipReader.findEntries(searchForEntries[i]);
  3614.         while (entries.hasMoreElements()) {
  3615.           var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  3616.           var target = getFile(KEY_TEMPDIR, [entry.name]);
  3617.           try {
  3618.             target.createUnique(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  3619.           }
  3620.           catch (e) {
  3621.             LOG("installMultiXPI: failed to create target file for extraction " +
  3622.                 " file = " + target.path + ", exception = " + e + "\n");
  3623.           }
  3624.           zipReader.extract(entry.name, target);
  3625.           files.push(target);
  3626.         }
  3627.       }
  3628.       zipReader.close();
  3629.  
  3630.       if (files.length == 0) {
  3631.         LOG("Multiple Item Package: Item: \"" + fileURL.fileName + "\" does " +
  3632.             "not contain a valid package to install.");
  3633.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3634.         showMessage("missingPackageFilesTitle",
  3635.                     [bundle.GetStringFromName("type-" + installData.type)],
  3636.                     "missingPackageFilesMessage", [installData.name,
  3637.                     bundle.GetStringFromName("type-" + installData.type)]);
  3638.         return;
  3639.       }
  3640.  
  3641.       // When installing themes we need to open the theme manager if it is not
  3642.       // already opened except when we are installing a xpi dropped into a
  3643.       // extensions directory.
  3644.       fileURL = getURIFromFile(files[files.length - 1]).QueryInterface(nsIURL);
  3645.       if (fileURL.fileExtension.toLowerCase() == "jar") {
  3646.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3647.                            .getService(Components.interfaces.nsIWindowMediator);
  3648.         var win = wm.getMostRecentWindow("Extension:Manager-extensions");
  3649.         if (win) {
  3650.           win = wm.getMostRecentWindow("Extension:Manager-themes");
  3651.           if (win) {
  3652.             win.focus();
  3653.           }
  3654.           else {
  3655.             var themeManagerURL = gPref.getCharPref(PREF_XPINSTALL_STATUS_DLG_SKIN);
  3656.             var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3657.                                .getService(Components.interfaces.nsIWindowWatcher);
  3658.             ww.openWindow(null, themeManagerURL, 
  3659.                           "", "chrome,centerscreen,titlebar,dialog=no,resizable", []);
  3660.           }
  3661.         }
  3662.       }
  3663.  
  3664.       for (i = 0; i < files.length; ++i) {
  3665.         em.installItemFromFileInternal(files[i], aInstallLocationKey, null);
  3666.         files[i].remove(false);
  3667.       }
  3668.     }
  3669.  
  3670.     /**
  3671.      * An observer for the Extension Update System.
  3672.      * @constructor
  3673.      */
  3674.     function IncompatibleObserver() {}
  3675.     IncompatibleObserver.prototype = {
  3676.       _id: null,
  3677.       _type: nsIUpdateItem.TYPE_ANY,
  3678.       _xpi: null,
  3679.       _installManifest: null,
  3680.       _installRDF: null,
  3681.       
  3682.       /** 
  3683.        * Ask the Extension Update System if there are any version updates for
  3684.        * this item that will allow it to be compatible with this version of 
  3685.        * the Application.
  3686.        * @param   installManifest 
  3687.        *          The Install Manifest datasource for the item.
  3688.        * @param   installData
  3689.        *          The Install Data object for the item.
  3690.        * @param   xpiFile         
  3691.        *          The staged source XPI file that contains the item. Cleaned 
  3692.        *          up by this process.
  3693.        */
  3694.       checkForUpdates: function(installManifest, installData, xpiFile, installRDF) {
  3695.         this._id              = installData.id;
  3696.         this._type            = installData.type;
  3697.         this._xpi             = xpiFile;
  3698.         this._installManifest = installManifest;
  3699.         this._installRDF      = installRDF;
  3700.         
  3701.         var item = makeItem(installData.id, installData.version, 
  3702.                             aInstallLocationKey, 
  3703.                             installData.currentApp.minVersion, 
  3704.                             installData.currentApp.maxVersion,
  3705.                             installData.name,
  3706.                             "", /* XPI Update URL */
  3707.                             "", /* XPI Update Hash */
  3708.                             "", /* Icon URL */
  3709.                             installData.updateURL || "", 
  3710.                             installData.type);
  3711.         em.update([item], 1, true, this);
  3712.       },
  3713.       
  3714.       /**
  3715.        * See nsIExtensionManager.idl
  3716.        */
  3717.       onUpdateStarted: function() {
  3718.         LOG("Phone Home Listener: Update Started");
  3719.         em.datasource.onUpdateStarted();
  3720.       },
  3721.       
  3722.       /**
  3723.        * See nsIExtensionManager.idl
  3724.        */
  3725.       onUpdateEnded: function() {
  3726.         LOG("Phone Home Listener: Update Ended");
  3727.         // We are responsible for cleaning up this file!
  3728.         this._installRDF.remove(false);
  3729.         em.datasource.onUpdateEnded();
  3730.       },
  3731.       
  3732.       /**
  3733.        * See nsIExtensionManager.idl
  3734.        */
  3735.       onAddonUpdateStarted: function(addon) {
  3736.         LOG("Phone Home Listener: Update For " + addon.id + " started");
  3737.         em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path,
  3738.                                                 addon.type, addon.version);
  3739.         em.datasource.onAddonUpdateStarted(addon);
  3740.       },
  3741.       
  3742.       /**
  3743.        * See nsIExtensionManager.idl
  3744.        */
  3745.       onAddonUpdateEnded: function(addon, status) {
  3746.         LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status); 
  3747.         em.datasource.removeDownload(this._xpi.path);
  3748.         LOG("Version Check Phone Home Completed");
  3749.         // Only compatibility updates (e.g. STATUS_VERSIONINFO) are currently
  3750.         // supported
  3751.         if (status == nsIAddonUpdateCheckListener.STATUS_VERSIONINFO) {
  3752.           em.datasource.setTargetApplicationInfo(addon.id, 
  3753.                                                  addon.minAppVersion,
  3754.                                                  addon.maxAppVersion, 
  3755.                                                  this._installManifest);
  3756.  
  3757.           // Try and install again, but use the updated compatibility DB
  3758.           em.installItemFromFileInternal(this._xpi, aInstallLocationKey, 
  3759.                                          this._installManifest);
  3760.  
  3761.           // Add the updated compatibility info to the datasource if done
  3762.           if (StartupCache.entries[aInstallLocationKey][addon.id].op == OP_NONE) {
  3763.             em.datasource.updateTargetAppInfo(addon.id, addon.minAppVersion,
  3764.                                               addon.maxAppVersion);
  3765.           }
  3766.           else { // needs a restart
  3767.             // Add updatedMinVersion and updatedMaxVersion so it can be used
  3768.             // to update the data sources during the installation or upgrade.
  3769.             em.datasource.setUpdatedTargetAppInfo(addon.id, addon.minAppVersion,
  3770.                                                   addon.maxAppVersion);
  3771.           }
  3772.           // Prevent the datasource file from being lazily recreated after
  3773.           // it is deleted by calling Flush.
  3774.           this._installManifest.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  3775.           this._installManifest.Flush();
  3776.         }
  3777.         else {
  3778.           em.datasource.removeDownload(this._xpi.path);
  3779.           showIncompatibleError(installData);
  3780.           // We are responsible for cleaning up this file!
  3781.           InstallLocations.get(aInstallLocationKey).removeFile(this._xpi);
  3782.         }
  3783.         em.datasource.onAddonUpdateEnded(addon, status);
  3784.       },
  3785.  
  3786.       /**
  3787.        * See nsISupports.idl
  3788.        */
  3789.       QueryInterface: function(iid) {
  3790.         if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
  3791.             !iid.equals(Components.interfaces.nsISupports))
  3792.           throw Components.results.NS_ERROR_NO_INTERFACE;
  3793.         return this;
  3794.       }
  3795.     }
  3796.  
  3797.     var installManifestFile = extractRDFFileToTempDir(aXPIFile, FILE_INSTALL_MANIFEST, true);
  3798.     var shouldPhoneHomeIfNecessary = false;
  3799.     if (!aInstallManifest) {
  3800.       // If we were not called with an Install Manifest, we were called from 
  3801.       // some other path than the Phone Home system, so we do want to phone
  3802.       // home if the version is incompatible.
  3803.       shouldPhoneHomeIfNecessary = true;
  3804.       var installManifest = getInstallManifest(installManifestFile);
  3805.       if (!installManifest) {
  3806.         LOG("The Install Manifest supplied by this item is not well-formed. " + 
  3807.             "Installation will not proceed.");
  3808.         installManifestFile.remove(false);
  3809.         return;
  3810.       }
  3811.     }
  3812.     else
  3813.       installManifest = aInstallManifest;
  3814.     
  3815.     var installData = this._getInstallData(installManifest);
  3816.     switch (installData.error) {
  3817.     case INSTALLERROR_INCOMPATIBLE_VERSION:
  3818.       // Since the caller cleans up |aXPIFile|, and we're not yet sure whether or
  3819.       // not we need it (we may need it if a remote version bump that makes it 
  3820.       // compatible is discovered by the call home) - so we must stage it for 
  3821.       // later ourselves.
  3822.       if (shouldPhoneHomeIfNecessary && installData.currentApp) {
  3823.         var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  3824.         if (!installLocation) {
  3825.           installManifestFile.remove(false);
  3826.           return;
  3827.         }
  3828.         var stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  3829.         (new IncompatibleObserver(this)).checkForUpdates(installManifest, 
  3830.                                                          installData, stagedFile,
  3831.                                                          installManifestFile);
  3832.         // Return early to prevent deletion of the install manifest file.
  3833.         return;
  3834.       }
  3835.       else {
  3836.         // XXXben Look up XULRunnerSettingsThingy to see if there is a registered
  3837.         //        app that can handle this item, if so just stage and don't show
  3838.         //        this error!
  3839.         showIncompatibleError(installData);
  3840.       }
  3841.       break;
  3842.     case INSTALLERROR_SUCCESS:
  3843.       // Installation of multiple extensions / themes contained within a single xpi.
  3844.       if (installData.type == nsIUpdateItem.TYPE_MULTI_XPI) {
  3845.         installMultiXPI(aXPIFile, installData);
  3846.         break;
  3847.       }
  3848.  
  3849.       // Stage the extension's XPI so it can be extracted at the next restart.
  3850.       var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  3851.       if (!installLocation) {
  3852.         // No cleanup of any of the staged XPI files should be required here, 
  3853.         // because this should only ever fail on the first recurse through
  3854.         // this function, BEFORE staging takes place... technically speaking
  3855.         // a location could become readonly during the phone home process, 
  3856.         // but that's an edge case I don't care about.
  3857.         installManifestFile.remove(false);
  3858.         return;
  3859.       }
  3860.  
  3861.       // Stage a copy of the XPI/JAR file for our own evil purposes...
  3862.       stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  3863.       
  3864.       var restartRequired = this.installRequiresRestart(installData.id, 
  3865.                                                         installData.type);
  3866.       // Determine which configuration function to use based on whether or not
  3867.       // there is data about this item in our datasource already - if there is 
  3868.       // we want to upgrade, otherwise we install fresh.
  3869.       var ds = this.datasource;
  3870.       if (installData.id in ds.visibleItems && ds.visibleItems[installData.id]) {
  3871.         // We enter this function if any data corresponding to an existing GUID
  3872.         // is found, regardless of its Install Location. We need to check before
  3873.         // "upgrading" an item that Install Location of the new item is of equal
  3874.         // or higher priority than the old item, to make sure the datasource only
  3875.         // ever tracks metadata for active items.
  3876.         var oldInstallLocation = this.getInstallLocation(installData.id);
  3877.         if (oldInstallLocation.priority >= installLocation.priority) {
  3878.           this._upgradeItem(installManifest, installData.id, installLocation, 
  3879.                             installData.type);
  3880.           if (!restartRequired) {
  3881.             this._finalizeUpgrade(installData.id);
  3882.             this._finalizeInstall(installData.id, stagedFile);
  3883.           }
  3884.         }
  3885.       }
  3886.       else {
  3887.         this._configureForthcomingItem(installManifest, installData.id, 
  3888.                                         installLocation, installData.type);
  3889.         if (!restartRequired)
  3890.           this._finalizeInstall(installData.id, stagedFile);
  3891.       }
  3892.       this._updateManifests(restartRequired);
  3893.       break;
  3894.     case INSTALLERROR_INVALID_GUID:
  3895.       LOG("Invalid GUID: Item has GUID: \"" + installData.id + "\"" + 
  3896.           " which is not well-formed.");
  3897.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3898.       showMessage("incompatibleTitle", 
  3899.                   [bundle.GetStringFromName("type-" + installData.type)], 
  3900.                   "invalidGUIDMessage", [installData.name, installData.id]);
  3901.       break;
  3902.     case INSTALLERROR_INVALID_VERSION:
  3903.       LOG("Invalid Version: Item: \"" + installData.id + "\" has version " + 
  3904.           installData.version + " which is not well-formed.");
  3905.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3906.       showMessage("incompatibleTitle", 
  3907.                   [bundle.GetStringFromName("type-" + installData.type)], 
  3908.                   "invalidVersionMessage", [installData.name, installData.version]);
  3909.       break;
  3910.     case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  3911.       const osABI = gOSTarget + "_" + gXPCOMABI;
  3912.       LOG("Incompatible Platform: Item: \"" + installData.id + "\" is not " + 
  3913.           "compatible with '" + osABI + "'.");
  3914.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3915.       showMessage("incompatibleTitle", 
  3916.                   [bundle.GetStringFromName("type-" + installData.type)], 
  3917.                   "incompatiblePlatformMessage",
  3918.                   [installData.name, BundleManager.appName, osABI]);
  3919.       break;
  3920.     default:
  3921.       break;
  3922.     }
  3923.     
  3924.     // Check to see if this item supports other applications and in that case
  3925.     // stage the the XPI file in the location specified by those applications.
  3926.     stageXPIForOtherApps(aXPIFile, installData);
  3927.  
  3928.     installManifestFile.remove(false);
  3929.   },
  3930.   
  3931.   /**
  3932.    * Whether or not this type's installation/uninstallation requires 
  3933.    * the application to be restarted.
  3934.    * @param   id
  3935.    *          The GUID of the item
  3936.    * @param   type
  3937.    *          The nsIUpdateItem type of the item
  3938.    * @returns true if installation of an item of this type requires a 
  3939.    *          restart.
  3940.    */
  3941.   installRequiresRestart: function(id, type) {
  3942.     switch (type) {
  3943.     case nsIUpdateItem.TYPE_EXTENSION:
  3944.       return true;
  3945.     case nsIUpdateItem.TYPE_THEME:
  3946.       var internalName = this.datasource.getItemProperty(id, "internalName");
  3947.       var needsRestart = false;
  3948.       if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
  3949.         needsRestart = internalName == gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  3950.       if (!needsRestart &&
  3951.           gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN))
  3952.         needsRestart = internalName == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
  3953.       return needsRestart;
  3954.     }
  3955.     return false;
  3956.   },
  3957.   
  3958.   /**
  3959.    * Perform initial configuration on an item that has just or will be 
  3960.    * installed. This inserts the item into the appropriate container in the
  3961.    * datasource, so that the application UI shows the item even if it will
  3962.    * not actually be installed until the next restart.
  3963.    * @param   installManifest 
  3964.    *          The Install Manifest datasource that describes this item.
  3965.    * @param   id          
  3966.    *          The GUID of this item.
  3967.    * @param   installLocation
  3968.    *          The Install Location where this item is installed.
  3969.    * @param   type
  3970.    *          The nsIUpdateItem type of this item. 
  3971.    */  
  3972.   _configureForthcomingItem: function(installManifest, id, installLocation, type) {
  3973.     var ds = this.datasource;
  3974.     ds.updateVisibleList(id, installLocation.name, false);
  3975.     var props = { name            : EM_L(getManifestProperty(installManifest, "name")),
  3976.                   version         : EM_L(getManifestProperty(installManifest, "version")),
  3977.                   installLocation : EM_L(installLocation.name),
  3978.                   type            : EM_I(type),
  3979.                   availableUpdateURL    : null,
  3980.                   availableUpdateHash   : null,
  3981.                   availableUpdateVersion: null };
  3982.     for (var p in props)
  3983.       ds.setItemProperty(id, EM_R(p), props[p]);
  3984.     ds.updateProperty(id, "displayDescription");
  3985.     ds.updateProperty(id, "availableUpdateURL");
  3986.     
  3987.     this._setOp(id, OP_NEEDS_INSTALL);
  3988.     
  3989.     // Insert it into the child list NOW rather than later because:
  3990.     // - extensions installed using the command line need to be a member
  3991.     //   of a container during the install phase for the code to be able
  3992.     //   to identify profile vs. global
  3993.     // - extensions installed through the UI should show some kind of
  3994.     //   feedback to indicate their presence is forthcoming (i.e. they
  3995.     //   will be available after a restart).
  3996.     ds.insertItemIntoContainer(id);
  3997.     
  3998.     this._notifyAction(id, EM_ITEM_INSTALLED);
  3999.   },
  4000.   
  4001.   /**
  4002.    * Perform configuration on an item that has just or will be upgraded.
  4003.    * @param   installManifest
  4004.    *          The Install Manifest datasource that describes this item.
  4005.    * @param   itemID
  4006.    *          The GUID of this item.
  4007.    * @param   installLocation
  4008.    *          The Install Location where this item is installed.
  4009.    * @param   type
  4010.    *          The nsIUpdateItem type of this item. 
  4011.    */
  4012.   _upgradeItem: function (installManifest, id, installLocation, type) {
  4013.     // Don't change any props that would need to be reset if the install fails.
  4014.     // They will be reset as appropriate by the upgrade/install process.
  4015.     var ds = this.datasource;
  4016.     ds.updateVisibleList(id, installLocation.name, false);
  4017.     var props = { installLocation : EM_L(installLocation.name),
  4018.                   type            : EM_I(type),
  4019.                   availableUpdateURL      : null,
  4020.                   availableUpdateHash     : null,
  4021.                   availableUpdateVersion  : null };
  4022.     for (var p in props)
  4023.       ds.setItemProperty(id, EM_R(p), props[p]);
  4024.     ds.updateProperty(id, "displayDescription");
  4025.     ds.updateProperty(id, "availableUpdateURL");
  4026.  
  4027.     this._setOp(id, OP_NEEDS_UPGRADE);
  4028.     this._notifyAction(id, EM_ITEM_UPGRADED);
  4029.   },
  4030.  
  4031.   /** 
  4032.    * Completes an Extension's installation.
  4033.    * @param   id
  4034.    *          The GUID of the Extension to install.
  4035.    * @param   file
  4036.    *          The XPI/JAR file to install from. If this is null, we try to
  4037.    *          determine the stage file location from the ID.
  4038.    */
  4039.   _finalizeInstall: function(id, file) {
  4040.     var ds = this.datasource;
  4041.     var type = ds.getItemProperty(id, "type");
  4042.     if (id == 0 || id == -1) {
  4043.       ds.removeCorruptItem(id, type);
  4044.       return;
  4045.     }
  4046.     var installLocation = this.getInstallLocation(id);
  4047.     if (!installLocation) {
  4048.       // If the install location is null, that means we've reached the finalize
  4049.       // state without the item ever having metadata added for it, which implies
  4050.       // bogus data in the Startup Cache. Clear the entries and don't do anything
  4051.       // else.
  4052.       var entries = StartupCache.findEntries(id);
  4053.       for (var i = 0; i < entries.length; ++i) {
  4054.         var location = InstallLocations.get(entries[i].location);
  4055.         StartupCache.clearEntry(location, id);
  4056.         PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4057.       }
  4058.       return;
  4059.     }
  4060.     var itemLocation = installLocation.getItemLocation(id);
  4061.  
  4062.     if (!file && "stageFile" in installLocation)
  4063.       file = installLocation.getStageFile(id);
  4064.     
  4065.     // If |file| is null or does not exist, the installer assumes the item is
  4066.     // a dropped-in directory.
  4067.     var installer = new Installer(this.datasource, id, installLocation, type);
  4068.     installer.installFromFile(file);
  4069.  
  4070.     // If the file was staged, we must clean it up ourselves, otherwise the 
  4071.     // EM caller is responsible for doing so (e.g. XPInstall)
  4072.     if (file)
  4073.       installLocation.removeFile(file);
  4074.     
  4075.     // Clear the op flag from the Startup Cache and Pending Operations sets
  4076.     StartupCache.put(installLocation, id, OP_NONE, true);
  4077.     PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4078.   },
  4079.  
  4080.   /**
  4081.    * Removes an item's metadata in preparation for an upgrade-install.
  4082.    * @param   id
  4083.    *          The GUID of the item to uninstall.
  4084.    */
  4085.   _finalizeUpgrade: function(id) {
  4086.     // Retrieve the item properties *BEFORE* we clean the resource!
  4087.     var ds = this.datasource;
  4088.     var installLocation = this.getInstallLocation(id);
  4089.  
  4090.     var stagedFile;
  4091.     if ("getStageFile" in installLocation)
  4092.       stagedFile = installLocation.getStageFile(id);
  4093.     else
  4094.       stagedFile = null;
  4095.  
  4096.     var stagedFileExists = stagedFile && stagedFile.exists();
  4097.     if (stagedFileExists)
  4098.       var installRDF = extractRDFFileToTempDir(stagedFile, FILE_INSTALL_MANIFEST, true);
  4099.     else
  4100.       installRDF = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  4101.     if (installRDF.exists()) {
  4102.       var installManifest = getInstallManifest(installRDF);
  4103.       if (installManifest) {
  4104.         var type = getAddonTypeFromInstallManifest(installManifest);
  4105.         var userDisabled = ds.getItemProperty(id, "userDisabled") == "true";
  4106.  
  4107.         // Clean the item resource
  4108.         ds.removeItemMetadata(id);
  4109.         // Now set up the properties on the item to mimic an item in its
  4110.         // "initial state" for installation.
  4111.         this._configureForthcomingItem(installManifest, id, installLocation, 
  4112.                                        type);
  4113.         if (userDisabled)
  4114.           ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  4115.       }
  4116.       if (stagedFileExists)
  4117.         installRDF.remove(false);
  4118.     }
  4119.     // Clear the op flag from the Pending Operations set. Do NOT clear op flag in 
  4120.     // the startup cache since this may have been reset to OP_NEEDS_INSTALL by
  4121.     // |_configureForthcomingItem|.
  4122.     PendingOperations.clearItem(OP_NEEDS_UPGRADE, id);
  4123.   },
  4124.   
  4125.   /**
  4126.    * Completes an item's uninstallation.
  4127.    * @param   id
  4128.    *          The GUID of the item to uninstall.
  4129.    */
  4130.   _finalizeUninstall: function(id) {
  4131.     var ds = this.datasource;
  4132.     
  4133.     var installLocation = this.getInstallLocation(id);
  4134.     if (!installLocation.itemIsManagedIndependently(id)) {
  4135.       try {
  4136.         // Having a callback that does nothing just causes the directory to be
  4137.         // removed.
  4138.         safeInstallOperation(id, installLocation, 
  4139.                              { data: null, callback: function() { } });
  4140.       }
  4141.       catch (e) {
  4142.         LOG("_finalizeUninstall: failed to remove directory for item: " + id + 
  4143.             " at Install Location: " + installLocation.name + ", rolling back uninstall");
  4144.         // Removal of the files failed, reset the uninstalled flag and rewrite
  4145.         // the install manifests so this item's components are registered.
  4146.         // Clear the op flag from the Startup Cache
  4147.         StartupCache.put(installLocation, id, OP_NONE, true);
  4148.         var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4149.         this._updateManifests(restartRequired);
  4150.         return;
  4151.       }
  4152.     }
  4153.     else if (installLocation.name == KEY_APP_PROFILE ||
  4154.              installLocation.name == KEY_APP_GLOBAL) {
  4155.       // Check for a pointer file and remove it if it exists
  4156.       var pointerFile = installLocation.location.clone();
  4157.       pointerFile.append(id);
  4158.       if (pointerFile.exists() && !pointerFile.isDirectory())
  4159.         pointerFile.remove(false);
  4160.     }
  4161.     
  4162.     // Clean the item resource
  4163.     ds.removeItemMetadata(id);
  4164.     
  4165.     // Do this LAST since inferences are made about an item based on
  4166.     // what container it's in.
  4167.     ds.removeItemFromContainer(id);
  4168.     
  4169.     // Clear the op flag from the Startup Cache and the Pending Operations set.
  4170.     StartupCache.clearEntry(installLocation, id);
  4171.     PendingOperations.clearItem(OP_NEEDS_UNINSTALL, id);
  4172.   },
  4173.   
  4174.   /**
  4175.    * Uninstalls an item. If the uninstallation cannot be performed immediately
  4176.    * it is scheduled for the next restart.
  4177.    * @param   id
  4178.    *          The GUID of the item to uninstall.
  4179.    */
  4180.   uninstallItem: function(id) {
  4181.     var em = this;
  4182.     /**
  4183.      * Cleans up any staged xpis for this item when we uninstall. This fixes 
  4184.      * this scenario:
  4185.      * 1 install Foo 1.0, and restart
  4186.      * 2 install Foo 1.1, 
  4187.      * 3 without restarting, uninstall Foo 1.1
  4188.      * 4 Foo is uninstalled but the staged XPI from the install operation in
  4189.      *   step 2 lingers, and is offered to the user on the next startup to
  4190.      *   be installed.
  4191.      * @param   id
  4192.      *          The GUID of the item to look for staged files for.
  4193.      */
  4194.     function cleanUpPendingStageFile(id) {
  4195.       var foundStageOp = false;
  4196.       var stageOps = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  4197.       stageOps = stageOps.concat(PendingOperations.getOperations(OP_NEEDS_INSTALL));
  4198.       for (var i = 0; i < stageOps.length; ++i) {
  4199.         if (stageOps[i].id == id) {
  4200.           foundStageOp = true;
  4201.           break;
  4202.         }
  4203.       }
  4204.       
  4205.       if (foundStageOp) {
  4206.         var location = em.getInstallLocation(id);
  4207.         var stageFile = location.getStageFile(id);
  4208.         location.removeFile(stageFile);
  4209.       }
  4210.     }
  4211.   
  4212.     var ds = this.datasource;
  4213.     var type = ds.getItemProperty(id, "type");
  4214.     if (!ds.isDownloadItem(id)) {
  4215.       cleanUpPendingStageFile(id);
  4216.       this._setOp(id, OP_NEEDS_UNINSTALL);
  4217.       var restartRequired = this.installRequiresRestart(id, type);
  4218.       if (!restartRequired) {
  4219.         this._finalizeUninstall(id);
  4220.         this._updateManifests(restartRequired);
  4221.       }
  4222.     }
  4223.     else {
  4224.       // Bad download entry - uri is url, e.g. "http://www.foo.com/test.xpi"
  4225.       // ... just remove it from the list. 
  4226.       ds.removeCorruptDLItem(id);
  4227.     }
  4228.     
  4229.     this._notifyAction(id, EM_ITEM_UNINSTALLED);
  4230.   },
  4231.  
  4232.   /**
  4233.    * Sets the pending operation for a visible item. 
  4234.    * @param   id
  4235.    *          The GUID of the item
  4236.    * @param   op
  4237.    *          The name of the operation to be performed
  4238.    */  
  4239.   _setOp: function(id, op) {
  4240.     var location = this.getInstallLocation(id);
  4241.     StartupCache.put(location, id, op, true);
  4242.     PendingOperations.addItem(op, { locationKey: location.name, id: id });
  4243.     var ds = this.datasource;
  4244.     ds.updateProperty(id, "opType");
  4245.     ds.updateProperty(id, "updateable");
  4246.     ds.updateProperty(id, "displayDescription");
  4247.     var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4248.     this._updateManifests(restartRequired);
  4249.   },
  4250.   
  4251.   /**
  4252.    * Enables an item for the application (e.g. the item meets all requirements
  4253.    * for it to be enabled). If the item is not disabled by the user this will
  4254.    * also set the needs-enable operation for the next restart.
  4255.    * @param   id
  4256.    *          The ID of the item to be enabled by the application.
  4257.    */
  4258.   _appEnableItem: function(id) {
  4259.     var ds = this.datasource;
  4260.     if (ds.getItemProperty(id, "userDisabled") != "true") {
  4261.       this._setOp(id, OP_NEEDS_ENABLE);
  4262.       this._notifyAction(id, EM_ITEM_ENABLED);
  4263.     }
  4264.     else {
  4265.       ds.setItemProperty(id, EM_R("appDisabled"), null);
  4266.       ds.updateProperty(id, "compatible");
  4267.       ds.updateProperty(id, "displayDescription");
  4268.     }
  4269.   },
  4270.  
  4271.   /**
  4272.    * Disables an item for the application (e.g. the item does not meets all
  4273.    * requirements like app compatibility for it to be enabled). If the item is
  4274.    * not disabled by the user this will also set the needs-disable operation
  4275.    * for the next restart.
  4276.    * @param   id
  4277.    *          The ID of the item to be disabled by the application.
  4278.    */
  4279.   _appDisableItem: function(id) {
  4280.     var ds = this.datasource;
  4281.     ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  4282.     if (ds.getItemProperty(id, "userDisabled") != "true") {
  4283.       this._setOp(id, OP_NEEDS_DISABLE);
  4284.       this._notifyAction(id, EM_ITEM_DISABLED);
  4285.     }
  4286.   },
  4287.     
  4288.   /**
  4289.    * Sets an item to be enabled by the user. If the item is already enabled this
  4290.    * clears the needs-enable operation for the next restart. If the item's
  4291.    * operation is set to needs-uninstall this will cancel the uninstall and set
  4292.    * the needs-enable operation for the next restart if the item is disabled.
  4293.    * 
  4294.    * @param   id
  4295.    *          The ID of the item to be enabled by the user.
  4296.    */
  4297.   enableItem: function(id) {
  4298.     var ds = this.datasource;
  4299.     if (ds.getItemProperty(id, "userDisabled") == "true" ||
  4300.         ds.getItemProperty(id, "appDisabled") == "true" &&
  4301.         ds.getItemProperty(id, "compatible") == "true") {
  4302.       this._setOp(id, OP_NEEDS_ENABLE);
  4303.       this._notifyAction(id, EM_ITEM_ENABLED);
  4304.     }
  4305.     else {
  4306.       this._setOp(id, OP_NONE);
  4307.       this._notifyAction(id, EM_ITEM_CANCEL);
  4308.     }
  4309.   },
  4310.   
  4311.   /**
  4312.    * Sets an item to be disabled by the user. If the item is already disabled
  4313.    * this clears the needs-disable operation for the next restart.
  4314.    * @param   id
  4315.    *          The ID of the item to be disabled by the user.
  4316.    */
  4317.   disableItem: function(id) {
  4318.     var ds = this.datasource;
  4319.     if (ds.getItemProperty(id, "userDisabled") == "true") {
  4320.       this._setOp(id, OP_NONE);
  4321.       this._notifyAction(id, EM_ITEM_CANCEL);
  4322.     }
  4323.     else {
  4324.       this._setOp(id, OP_NEEDS_DISABLE);
  4325.       this._notifyAction(id, EM_ITEM_DISABLED);
  4326.     }
  4327.   },
  4328.   
  4329.   /**
  4330.    * Notify observers of a change to an item that has been requested by the
  4331.    * user. 
  4332.    */
  4333.   _notifyAction: function(id, reason) {
  4334.     gOS.notifyObservers(this.datasource.getItemForID(id), 
  4335.                         EM_ACTION_REQUESTED_TOPIC, reason);
  4336.   },
  4337.   
  4338.   /**
  4339.    * See nsIExtensionManager.idl
  4340.    */
  4341.   update: function(items, itemCount, versionUpdateOnly, listener) {
  4342.     var appID = gApp.ID;
  4343.     var appVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  4344.                              gApp.version);
  4345.  
  4346.     if (items.length == 0)
  4347.       items = this.getItemList(nsIUpdateItem.TYPE_ADDON, { });
  4348.  
  4349.     var updater = new ExtensionItemUpdater(appID, appVersion, this);
  4350.     updater.checkForUpdates(items, items.length, versionUpdateOnly, listener);
  4351.   },
  4352.  
  4353.   /**
  4354.    * @returns An enumeration of all registered Install Locations.
  4355.    */
  4356.   get installLocations () {
  4357.     return InstallLocations.enumeration;
  4358.   },
  4359.   
  4360.   /**
  4361.    * Gets the Install Location where a visible Item is stored.
  4362.    * @param   id
  4363.    *          The GUID of the item to locate an Install Location for.
  4364.    * @returns The Install Location object where the item is stored.
  4365.    */
  4366.   getInstallLocation: function(id) {
  4367.     var key = this.datasource.visibleItems[id];
  4368.     return key ? InstallLocations.get(this.datasource.visibleItems[id]) : null;
  4369.   },
  4370.   
  4371.   /**
  4372.    * Gets a nsIUpdateItem for the item with the specified id.
  4373.    * @param   id
  4374.    *          The GUID of the item to construct a nsIUpdateItem for.
  4375.    * @returns The nsIUpdateItem representing the item.
  4376.    */
  4377.   getItemForID: function(id) {
  4378.     return this.datasource.getItemForID(id);
  4379.   },
  4380.   
  4381.   /**
  4382.    * Retrieves a list of nsIUpdateItems of items matching the specified type.
  4383.    * @param   type
  4384.    *          The type of item to return.
  4385.    * @param   countRef
  4386.    *          The XPCJS reference to the number of items returned.
  4387.    * @returns An array of nsIUpdateItems matching the id/type filter.
  4388.    */
  4389.   getItemList: function(type, countRef) {
  4390.     return this.datasource.getItemList(type, countRef);
  4391.   },
  4392.  
  4393.   /**  
  4394.    * See nsIExtensionManager.idl
  4395.    */
  4396.   getIncompatibleItemList: function(id, version, type, includeDisabled, 
  4397.                                     countRef) {
  4398.     var items = this.datasource.getIncompatibleItemList(id, version ? version : undefined,
  4399.                                                         type, includeDisabled);
  4400.     countRef.value = items.length;
  4401.     return items;
  4402.   },
  4403.   
  4404.   /**
  4405.    * Move an Item to the index of another item in its container.
  4406.    * @param   movingID
  4407.    *          The ID of the item to be moved.
  4408.    * @param   destinationID
  4409.    *          The ID of an item to move another item to.
  4410.    */
  4411.   moveToIndexOf: function(movingID, destinationID) {
  4412.     this.datasource.moveToIndexOf(movingID, destinationID);
  4413.   },
  4414.  
  4415.   /////////////////////////////////////////////////////////////////////////////    
  4416.   // Downloads
  4417.   _transactions: [],
  4418.   _downloadCount: 0,
  4419.   
  4420.   /**
  4421.    * Ask the user if they really want to quit the application, since this will 
  4422.    * cancel one or more Extension/Theme downloads.
  4423.    * @param   subject
  4424.    *          A nsISupportsPRBool which this function sets to false if the user
  4425.    *          wishes to cancel all active downloads and quit the application,
  4426.    *          false otherwise.
  4427.    */
  4428.   _confirmCancelDownloadsOnQuit: function(subject) {
  4429.     if (this._downloadCount > 0) {
  4430.       var result;
  4431. //@line 4596 "/var/tmp/portage/mozilla-firefox-1.5.0.3/work/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4432.       result = this._confirmCancelDownloads(this._downloadCount, 
  4433.                                             "quitCancelDownloadsAlertTitle",
  4434.                                             "quitCancelDownloadsAlertMsgMultiple",
  4435.                                             "quitCancelDownloadsAlertMsg",
  4436.                                             "dontQuitButtonWin");
  4437. //@line 4608 "/var/tmp/portage/mozilla-firefox-1.5.0.3/work/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4438.       if (!result)
  4439.         this._cancelDownloads();
  4440.       if (subject instanceof Components.interfaces.nsISupportsPRBool)
  4441.         subject.data = result;
  4442.     }
  4443.   },
  4444.   
  4445.   /**
  4446.    * Ask the user if they really want to go offline, since this will cancel 
  4447.    * one or more Extension/Theme downloads.
  4448.    * @param   subject
  4449.    *          A nsISupportsPRBool which this function sets to false if the user
  4450.    *          wishes to cancel all active downloads and go offline, false
  4451.    *          otherwise.
  4452.    */
  4453.   _confirmCancelDownloadsOnOffline: function(subject) {
  4454.     if (this._downloadCount > 0) {
  4455.       result = this._confirmCancelDownloads(this._downloadCount,
  4456.                                             "offlineCancelDownloadsAlertTitle",
  4457.                                             "offlineCancelDownloadsAlertMsgMultiple",
  4458.                                             "offlineCancelDownloadsAlertMsg",
  4459.                                             "dontGoOfflineButton");
  4460.       if (!result)
  4461.         this._cancelDownloads();
  4462.       var PRBool = subject.QueryInterface(Components.interfaces.nsISupportsPRBool);
  4463.       PRBool.data = result;
  4464.     }
  4465.   },
  4466.   
  4467.   /**
  4468.    * Cancels all active downloads and removes them from the applicable UI.
  4469.    */
  4470.   _cancelDownloads: function() {
  4471.     for (var i = 0; i < this._transactions.length; ++i)
  4472.       gOS.notifyObservers(this._transactions[i], "xpinstall-progress", "cancel");
  4473.     gOS.removeObserver(this, "offline-requested");
  4474.     gOS.removeObserver(this, "quit-application-requested");
  4475.  
  4476.     this._removeAllDownloads();
  4477.   },
  4478.  
  4479.   /**
  4480.    * Ask the user whether or not they wish to cancel the Extension/Theme
  4481.    * downloads which are currently under way.
  4482.    * @param   count
  4483.    *          The number of active downloads.
  4484.    * @param   title
  4485.    *          The key of the title for the message box to be displayed
  4486.    * @param   cancelMessageMultiple
  4487.    *          The key of the message to be displayed in the message box
  4488.    *          when there are > 1 active downloads.
  4489.    * @param   cancelMessageSingle
  4490.    *          The key of the message to be displayed in the message box
  4491.    *          when there is just one active download.
  4492.    * @param   dontCancelButton
  4493.    *          The key of the label to be displayed on the "Don't Cancel 
  4494.    *          Downloads" button.
  4495.    */
  4496.   _confirmCancelDownloads: function(count, title, cancelMessageMultiple, 
  4497.                                     cancelMessageSingle, dontCancelButton) {
  4498.     var bundle = BundleManager.getBundle(URI_DOWNLOADS_PROPERTIES);
  4499.     var title = bundle.GetStringFromName(title);
  4500.     var message, quitButton;
  4501.     if (count > 1) {
  4502.       message = bundle.formatStringFromName(cancelMessageMultiple, [count], 1);
  4503.       quitButton = bundle.formatStringFromName("cancelDownloadsOKTextMultiple", [count], 1);
  4504.     }
  4505.     else {
  4506.       message = bundle.GetStringFromName(cancelMessageSingle);
  4507.       quitButton = bundle.GetStringFromName("cancelDownloadsOKText");
  4508.     }
  4509.     var dontQuitButton = bundle.GetStringFromName(dontCancelButton);
  4510.     
  4511.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  4512.                        .getService(Components.interfaces.nsIWindowMediator);
  4513.     var win = wm.getMostRecentWindow("Extension:Manager");
  4514.     const nsIPromptService = Components.interfaces.nsIPromptService;
  4515.     var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  4516.                        .getService(nsIPromptService);
  4517.     var flags = (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_0) +
  4518.                 (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_1);
  4519.     var rv = ps.confirmEx(win, title, message, flags, quitButton, dontQuitButton, null, null, { });
  4520.     return rv == 1;
  4521.   },
  4522.   
  4523.   /** 
  4524.    * Adds a set of Item Downloads to the Manager and starts the download
  4525.    * operation.
  4526.    * @param   items
  4527.    *          An array of nsIUpdateItems to begin downlading.
  4528.    * @param   itemCount
  4529.    *          The length of |items|
  4530.    * @param   fromChrome
  4531.    *          true when called from chrome
  4532.    *          false when not called from chrome (e.g. web page)
  4533.    */
  4534.   addDownloads: function(items, itemCount, fromChrome) { 
  4535.     this._downloadCount += itemCount;
  4536.     
  4537.     var urls = [];
  4538.     var hashes = [];
  4539.     var txn = new ItemDownloadTransaction(this);
  4540.     for (var i = 0; i < itemCount; ++i) {
  4541.       var currItem = items[i];
  4542.       var txnID = Math.round(Math.random() * 100);
  4543.       txn.addDownload(currItem, txnID);
  4544.       this._transactions.push(txn);
  4545.       urls.push(currItem.xpiURL);
  4546.       hashes.push(currItem.xpiHash ? currItem.xpiHash : null);
  4547.     }
  4548.  
  4549.     // Kick off the download process for this transaction
  4550.     gOS.addObserver(this, "offline-requested", false);
  4551.     gOS.addObserver(this, "quit-application-requested", false);
  4552.     
  4553.     if (fromChrome) {
  4554.       // Initiate an install from chrome
  4555.       var xpimgr = 
  4556.           Components.classes["@mozilla.org/xpinstall/install-manager;1"].
  4557.           createInstance(Components.interfaces.nsIXPInstallManager);
  4558.       xpimgr.initManagerWithHashes(urls, hashes, urls.length, txn);
  4559.     }
  4560.     else
  4561.       gOS.notifyObservers(txn, "xpinstall-progress", "open");
  4562.   },
  4563.   
  4564.   /**
  4565.    * Removes a download of a URL.
  4566.    * @param   url
  4567.    *          The URL of the item being downloaded to remove.
  4568.    */
  4569.   removeDownload: function(url) {
  4570.     for (var i = 0; i < this._transactions.length; ++i) {
  4571.       if (this._transactions[i].containsURL(url)) {
  4572.         this._transactions[i].removeDownload(url);
  4573.         return;
  4574.       }
  4575.     } 
  4576.   },
  4577.   
  4578.   /**
  4579.    * Remove all downloads from all transactions.
  4580.    */
  4581.   _removeAllDownloads: function() {
  4582.     for (var i = 0; i < this._transactions.length; ++i)
  4583.       this._transactions[i].removeAllDownloads();
  4584.   },
  4585.  
  4586.   /**
  4587.    * Download Operation State has changed from one to another. 
  4588.    * 
  4589.    * The nsIXPIProgressDialog implementation in the download transaction object
  4590.    * forwards notifications through these methods which we then pass on to any
  4591.    * front end objects implementing nsIExtensionDownloadListener that 
  4592.    * are listening. We maintain the master state of download operations HERE, 
  4593.    * not in the front end, because if the user closes the extension or theme 
  4594.    * managers during the downloads we need to maintain state and not terminate
  4595.    * the download/install process. 
  4596.    *
  4597.    * @param   transaction
  4598.    *          The ItemDownloadTransaction object receiving the download 
  4599.    *          notifications from XPInstall.
  4600.    * @param   addon
  4601.    *          An object representing nsIUpdateItem for the addon being updated
  4602.    * @param   state
  4603.    *          The state we are entering
  4604.    * @param   value
  4605.    *          ???
  4606.    */
  4607.   onStateChange: function(transaction, addon, state, value) {
  4608.     var url = addon.xpiURL;
  4609.     if (!(url in this._progressData)) 
  4610.       this._progressData[url] = { };
  4611.     this._progressData[url].state = state;
  4612.     
  4613.     for (var i = 0; i < this._updateListeners.length; ++i)
  4614.       this._updateListeners[i].onStateChange(addon, state, value);
  4615.  
  4616.     const nsIXPIProgressDialog = Components.interfaces.nsIXPIProgressDialog;
  4617.     switch (state) {
  4618.     case nsIXPIProgressDialog.INSTALL_DONE:
  4619.       --this._downloadCount;
  4620.       break;
  4621.     case nsIXPIProgressDialog.DIALOG_CLOSE:
  4622.       for (var i = 0; i < this._transactions.length; ++i) {
  4623.         if (this._transactions[i].id == transaction.id) {
  4624.           this._transactions.splice(i, 1);
  4625.           delete transaction;
  4626.           break;
  4627.         }
  4628.       }
  4629.       break;
  4630.     }
  4631.     // If we're updating an installed item for which content is already built,
  4632.     // update the "displayDescription" property so the restart now message is
  4633.     // shown.
  4634.     if (addon.id != addon.xpiURL) {
  4635.       var ds = this.datasource;
  4636.       ds.updateProperty(addon.id, "displayDescription");
  4637.       ds.updateProperty(addon.id, "availableUpdateURL");
  4638.       ds.updateProperty(addon.id, "updateable"); 
  4639.     }
  4640.   },
  4641.   
  4642.   _progressData: { },
  4643.   onProgress: function(addon, value, maxValue) {
  4644.     for (var i = 0; i < this._updateListeners.length; ++i)
  4645.       this._updateListeners[i].onProgress(addon, value, maxValue);
  4646.     
  4647.     var url = addon.xpiURL;
  4648.     if (!(url in this._progressData)) 
  4649.       this._progressData[url] = { };
  4650.     this._progressData[url].progress = Math.round((value / maxValue) * 100);
  4651.   },
  4652.  
  4653.   _updateListeners: [],
  4654.   addUpdateListener: function(listener) {
  4655.     for (var i = 0; i < this._updateListeners.length; ++i) {
  4656.       if (this._updateListeners[i] == listener)
  4657.         return i;
  4658.     }
  4659.     this._updateListeners.push(listener);
  4660.     return this._updateListeners.length - 1;
  4661.   },
  4662.   
  4663.   removeUpdateListenerAt: function(index) {
  4664.     this._updateListeners.splice(index, 1);
  4665.     if (this._downloadCount != 0)
  4666.       this.datasource.flushProgressInfo(this._progressData);
  4667.   },
  4668.  
  4669.   /**
  4670.    * The Extensions RDF Datasource
  4671.    */
  4672.   _ds: null,
  4673.  
  4674.   /** 
  4675.    * Loads the Extensions Datasource. This should not be called unless: 
  4676.    * - a piece of Extensions UI is being shown, or
  4677.    * - on startup and there has been a change to an Install Location
  4678.    * ... it should NOT be called on every startup!
  4679.    */
  4680.   _ensureDS: function() {
  4681.     if (!this._ds) {
  4682.       this._ds = new ExtensionsDataSource(this);
  4683.       if (this._ds)
  4684.         this._ds.loadExtensions();
  4685.     }
  4686.   },
  4687.  
  4688.   /**
  4689.    * See nsIExtensionManager.idl
  4690.    */
  4691.   get datasource() {
  4692.     this._ensureDS();
  4693.     return this._ds.QueryInterface(Components.interfaces.nsIRDFDataSource);
  4694.   },
  4695.   
  4696.   /**
  4697.    * See nsIClassInfo.idl
  4698.    */
  4699.   getInterfaces: function(count) {
  4700.     var interfaces = [Components.interfaces.nsIExtensionManager,
  4701.                       Components.interfaces.nsIXPIProgressDialog,
  4702.                       Components.interfaces.nsIObserver];
  4703.     count.value = interfaces.length;
  4704.     return interfaces;
  4705.   },
  4706.   getHelperForLanguage: function(language) { 
  4707.     return null;
  4708.   },
  4709.   get contractID() {
  4710.     return "@mozilla.org/extensions/manager;1";
  4711.   },
  4712.   get classDescription() {
  4713.     return "Extension Manager";
  4714.   },
  4715.   get classID() {
  4716.     return Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}");
  4717.   },
  4718.   get implementationLanguage() {
  4719.     return Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT;
  4720.   },
  4721.   get flags() {
  4722.     return Components.interfaces.nsIClassInfo.SINGLETON;
  4723.   },
  4724.  
  4725.   /**
  4726.    * See nsISupports.idl
  4727.    */
  4728.   QueryInterface: function(iid) {
  4729.     if (!iid.equals(Components.interfaces.nsIExtensionManager) &&
  4730.         !iid.equals(Components.interfaces.nsITimerCallback) &&
  4731.         !iid.equals(Components.interfaces.nsIObserver) &&
  4732.         !iid.equals(Components.interfaces.nsISupports))
  4733.       throw Components.results.NS_ERROR_NO_INTERFACE;
  4734.     return this;
  4735.   }
  4736. };
  4737.  
  4738. /**
  4739.  * This object implements nsIXPIProgressDialog and represents a collection of
  4740.  * XPI/JAR download and install operations. There is one 
  4741.  * ItemDownloadTransaction per back-end XPInstallManager object. We maintain
  4742.  * a collection of separate transaction objects because it's possible to have
  4743.  * multiple separate XPInstall download/install operations going on 
  4744.  * simultaneously, each with its own XPInstallManager instance. For instance
  4745.  * you could start downloading two extensions and then download a theme. Each
  4746.  * of these operations would open the appropriate FE and have to be able to
  4747.  * track each operation independently.
  4748.  * 
  4749.  * @constructor
  4750.  */
  4751. function ItemDownloadTransaction(manager) {
  4752.   this._manager = manager;
  4753.   this._downloads = [];
  4754. }
  4755. ItemDownloadTransaction.prototype = {
  4756.   _manager    : null,
  4757.   _downloads  : [],
  4758.   id          : -1,
  4759.   
  4760.   /**
  4761.    * Add a download to this transaction
  4762.    * @param   addon
  4763.    *          An object implementing nsIUpdateItem for the item to be downloaded
  4764.    * @param   id
  4765.    *          The integer identifier of this transaction
  4766.    */
  4767.   addDownload: function(addon, id) {
  4768.     this._downloads.push({ addon: addon, waiting: true });
  4769.     this._manager.datasource.addDownload(addon);
  4770.     this.id = id;
  4771.   },
  4772.   
  4773.   /**
  4774.    * Removes a download from this transaction
  4775.    * @param   url
  4776.    *          The URL to remove
  4777.    */
  4778.   removeDownload: function(url) {
  4779.     this._manager.datasource.removeDownload(url);
  4780.   },
  4781.   
  4782.   /**
  4783.    * Remove all downloads from this transaction
  4784.    */
  4785.   removeAllDownloads: function() {
  4786.     for (var i = 0; i < this._downloads.length; ++i) {
  4787.       var addon = this._downloads[i].addon;
  4788.       this.removeDownload(addon.xpiURL, addon.type);
  4789.     }
  4790.   },
  4791.   
  4792.   /**
  4793.    * Determine if this transaction is handling the download of a url.
  4794.    * @param   url
  4795.    *          The URL to look for
  4796.    * @returns true if this transaction is downloading the supplied url.
  4797.    */
  4798.   containsURL: function(url) {
  4799.     for (var i = 0; i < this._downloads.length; ++i) {
  4800.       if (this._downloads[i].addon.xpiURL == url)
  4801.         return true;
  4802.     }
  4803.     return false;
  4804.   },
  4805.  
  4806.   /**
  4807.    * See nsIXPIProgressDialog.idl
  4808.    */
  4809.   onStateChange: function(index, state, value) {
  4810.     this._manager.onStateChange(this, this._downloads[index].addon, 
  4811.                                 state, value);
  4812.   },
  4813.   
  4814.   /**
  4815.    * See nsIXPIProgressDialog.idl
  4816.    */
  4817.   onProgress: function(index, value, maxValue) { 
  4818.     this._manager.onProgress(this._downloads[index].addon, value, maxValue);
  4819.   },
  4820.   
  4821.   /////////////////////////////////////////////////////////////////////////////
  4822.   // nsISupports
  4823.   QueryInterface: function(iid) {
  4824.     if (!iid.equals(Components.interfaces.nsIXPIProgressDialog) &&
  4825.         !iid.equals(Components.interfaces.nsISupports))
  4826.       throw Components.results.NS_ERROR_NO_INTERFACE;
  4827.     return this;
  4828.   }
  4829. };
  4830.  
  4831. /**
  4832.  * A listener object to the update check process that routes notifications to
  4833.  * the right places and keeps the datasource up to date.
  4834.  */
  4835. function AddonUpdateCheckListener(listener, datasource) {
  4836.   this._listener = listener;
  4837.   this._ds = datasource;
  4838. }
  4839. AddonUpdateCheckListener.prototype = {
  4840.   _listener: null,
  4841.   _ds: null,
  4842.   
  4843.   onUpdateStarted: function() {
  4844.     if (this._listener)
  4845.       this._listener.onUpdateStarted();
  4846.     this._ds.onUpdateStarted();
  4847.   },
  4848.   
  4849.   onUpdateEnded: function() {
  4850.     if (this._listener)
  4851.       this._listener.onUpdateEnded();
  4852.     this._ds.onUpdateEnded();
  4853.   },
  4854.   
  4855.   onAddonUpdateStarted: function(addon) {
  4856.     if (this._listener)
  4857.       this._listener.onAddonUpdateStarted(addon);
  4858.     this._ds.onAddonUpdateStarted(addon);
  4859.   },
  4860.   
  4861.   onAddonUpdateEnded: function(addon, status) {
  4862.     if (this._listener)
  4863.       this._listener.onAddonUpdateEnded(addon, status);
  4864.     this._ds.onAddonUpdateEnded(addon, status);
  4865.   }
  4866. };
  4867.  
  4868. ///////////////////////////////////////////////////////////////////////////////
  4869. //
  4870. // ExtensionItemUpdater
  4871. //
  4872. function ExtensionItemUpdater(aTargetAppID, aTargetAppVersion, aEM) 
  4873. {
  4874.   this._appID = aTargetAppID;
  4875.   this._appVersion = aTargetAppVersion;
  4876.   this._emDS = aEM._ds;
  4877.   this._em = aEM;
  4878.  
  4879.   getVersionChecker();
  4880. }
  4881.  
  4882. ExtensionItemUpdater.prototype = {
  4883.   _appID              : "",
  4884.   _appVersion         : "",
  4885.   _emDS               : null,
  4886.   _em                 : null,
  4887.   _versionUpdateOnly  : 0,
  4888.   _items              : [],
  4889.   _listener           : null,
  4890.   
  4891.   /////////////////////////////////////////////////////////////////////////////
  4892.   // ExtensionItemUpdater
  4893.   //
  4894.   // When we check for updates to an item, there are two pieces of information
  4895.   // that are returned - 1) info about the newest available version, if any,
  4896.   // and 2) info about the currently installed version. The latter is provided
  4897.   // primarily to inform the client of changes to the application compatibility 
  4898.   // metadata for the current item. Depending on the situation, either 2 or 
  4899.   // 1&2 may be what is required.
  4900.   //
  4901.   // Callers:
  4902.   //  1 - nsUpdateService.js, user event
  4903.   //      User clicked on the update icon to invoke an update check, 
  4904.   //      user clicked on an Extension/Theme and clicked "Update". In this
  4905.   //      case we want to update compatibility metadata about the installed
  4906.   //      version, and look for newer versions to offer. 
  4907.   //  2 - nsUpdateService.js, background event
  4908.   //      Timer fired, background update is being performed. In this case
  4909.   //      we also want to update compatibility metadata and look for newer
  4910.   //      versions.
  4911.   //  3 - Mismatch
  4912.   //      User upgraded to a newer version of the app, update compatibility
  4913.   //      metadata and look for newer versions.
  4914.   //  4 - Install Phone Home
  4915.   //      User installed an item that was deemed incompatible based only
  4916.   //      on the information provided in the item's install.rdf manifest, 
  4917.   //      we look ONLY for compatibility updates in this case to determine
  4918.   //      whether or not the item can be installed.
  4919.   //  
  4920.   checkForUpdates: function(aItems, aItemCount, aVersionUpdateOnly, 
  4921.                             aListener) {
  4922.     this._listener = new AddonUpdateCheckListener(aListener, this._emDS);
  4923.     if (this._listener)
  4924.       this._listener.onUpdateStarted();
  4925.     this._versionUpdateOnly = aVersionUpdateOnly;
  4926.     this._items = aItems;
  4927.     this._responseCount = aItemCount;
  4928.     
  4929.     // This is the number of extensions/themes/etc that we found updates for.
  4930.     this._updateCount = 0;
  4931.  
  4932.     for (var i = 0; i < aItemCount; ++i) {
  4933.       var e = this._items[i];
  4934.       if (this._listener)
  4935.         this._listener.onAddonUpdateStarted(e);
  4936.       (new RDFItemUpdater(this)).checkForUpdates(e, aVersionUpdateOnly);
  4937.     }
  4938.   },
  4939.   
  4940.   /////////////////////////////////////////////////////////////////////////////
  4941.   // ExtensionItemUpdater
  4942.   _applyVersionUpdates: function(aLocalItem, aRemoteItem) {
  4943.     var targetAppInfo = this._emDS.getTargetApplicationInfo(aLocalItem.id, this._emDS);
  4944.     // If targetAppInfo is null this is for a new install. If the local item's
  4945.     // maxVersion does not equal the targetAppInfo maxVersion then this is for
  4946.     // an upgrade. In both of these cases return true if the remotely specified
  4947.     // maxVersion is greater than the local item's maxVersion.
  4948.     if (!targetAppInfo ||
  4949.         gVersionChecker.compare(aLocalItem.maxAppVersion, targetAppInfo.maxVersion) != 0) {
  4950.       if (gVersionChecker.compare(aLocalItem.maxAppVersion, aRemoteItem.maxAppVersion) < 0)
  4951.         return true;
  4952.       else
  4953.         return false;
  4954.     }
  4955.  
  4956.     if (gVersionChecker.compare(targetAppInfo.maxVersion, aRemoteItem.maxAppVersion) < 0) {
  4957.       // Remotely specified maxVersion is newer than the maxVersion 
  4958.       // for the installed Extension. Apply that change to the datasources.
  4959.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  4960.                                      aRemoteItem.maxAppVersion);
  4961.  
  4962.       // If we got here through |checkForMismatches|, this extension has
  4963.       // already been disabled, re-enable it.
  4964.       var op = StartupCache.entries[aLocalItem.installLocationKey][aLocalItem.id].op;
  4965.       if (op == OP_NEEDS_DISABLE ||
  4966.           this._emDS.getItemProperty(aLocalItem.id, "appDisabled") == "true")
  4967.         this._em._appEnableItem(aLocalItem.id);
  4968.       return true;
  4969.     }
  4970.     else if (this._versionUpdateOnly == 2)
  4971.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  4972.                                      aRemoteItem.maxAppVersion);
  4973.     return false;
  4974.   },
  4975.   
  4976.   _isValidUpdate: function(aLocalItem, aRemoteItem) {
  4977.     var appExtensionsVersion =
  4978.       getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION, gApp.version);
  4979.  
  4980.     // Check if the update will only run on a newer version of Firefox. 
  4981.     if (aRemoteItem.minAppVersion && 
  4982.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.minAppVersion) < 0) 
  4983.       return false;
  4984.  
  4985.     // Check if the update will only run on an older version of Firefox. 
  4986.     if (aRemoteItem.maxAppVersion && 
  4987.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.maxAppVersion) > 0) 
  4988.       return false;
  4989.     
  4990.     return true;
  4991.   },
  4992.   
  4993.   checkForDone: function(item, status) {
  4994.     if (this._listener) {
  4995.       try {
  4996.         this._listener.onAddonUpdateEnded(item, status);
  4997.       }
  4998.       catch (e) {
  4999.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onAddonUpdateEnded: " + e);
  5000.       }
  5001.     }
  5002.     if (--this._responseCount == 0 && this._listener) {
  5003.       try {
  5004.         this._listener.onUpdateEnded();
  5005.       }
  5006.       catch (e) {
  5007.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e);
  5008.       }
  5009.     }
  5010.   },
  5011. };
  5012.  
  5013. function RDFItemUpdater(aUpdater) {
  5014.   this._updater = aUpdater;
  5015. }
  5016.  
  5017. RDFItemUpdater.prototype = {
  5018.   _updater            : null,
  5019.   _versionUpdateOnly  : 0,
  5020.   _item               : null,
  5021.   
  5022.   checkForUpdates: function(aItem, aVersionUpdateOnly) {
  5023.     // A preference setting can disable updating for this item
  5024.     try {
  5025.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, aItem.id))) {
  5026.         var status = nsIAddonUpdateCheckListener.STATUS_DISABLED;
  5027.         this._updater.checkForDone(aItem, status);
  5028.         return;
  5029.       }
  5030.     }
  5031.     catch (e) { }
  5032.  
  5033.     // Items managed by the app are not checked for updates.
  5034.     var emDS = this._updater._emDS;
  5035.     if (emDS.getItemProperty(aItem.id, "appManaged") == "true") {
  5036.       var status = nsIAddonUpdateCheckListener.STATUS_APP_MANAGED;
  5037.       this._updater.checkForDone(aItem, status);
  5038.       return;
  5039.     }
  5040.  
  5041.     // Items that have a pending install, uninstall, or upgrade are not checked
  5042.     // for updates.
  5043.     var opType = emDS.getItemProperty(aItem.id, "opType");
  5044.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  5045.         opType == OP_NEEDS_UPGRADE) {
  5046.       var status = nsIAddonUpdateCheckListener.STATUS_PENDING_OP;
  5047.       this._updater.checkForDone(aItem, status);
  5048.       return;
  5049.     }
  5050.  
  5051.     var installLocation = InstallLocations.get(emDS.getInstallLocationKey(aItem.id));
  5052.     // Don't check items for updates that are installed in a location that is
  5053.     // not managed by the app.
  5054.     if (installLocation && (installLocation.name == "winreg-app-global" ||
  5055.         installLocation.name == "winreg-app-user")) {
  5056.       var status = nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED;
  5057.       this._updater.checkForDone(aItem, status);
  5058.       return;
  5059.     }
  5060.  
  5061.     // Don't check items for updates if the location can't be written to except
  5062.     // when performing a version only update.
  5063.     if (!aVersionUpdateOnly && (!installLocation || !installLocation.canAccess)) {
  5064.       var status = nsIAddonUpdateCheckListener.STATUS_READ_ONLY;
  5065.       this._updater.checkForDone(aItem, status);
  5066.       return;
  5067.     }
  5068.  
  5069.     this._versionUpdateOnly = aVersionUpdateOnly;
  5070.     this._item = aItem;
  5071.   
  5072.     // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the
  5073.     // install manifest, 3) the default configuration
  5074.     try {
  5075.       var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id),
  5076.                                         Components.interfaces.nsIPrefLocalizedString).data;
  5077.     }
  5078.     catch (e) { }
  5079.     if (!dsURI)
  5080.       dsURI = aItem.updateRDF;
  5081.     if (!dsURI) {
  5082.       dsURI = gPref.getComplexValue(PREF_UPDATE_DEFAULT_URL,
  5083.                                     Components.interfaces.nsIPrefLocalizedString).data;
  5084.     }
  5085.     dsURI = dsURI.replace(/%ITEM_ID%/g, aItem.id);
  5086.     dsURI = dsURI.replace(/%ITEM_VERSION%/g, aItem.version);
  5087.     dsURI = dsURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
  5088.     dsURI = dsURI.replace(/%APP_ID%/g, this._updater._appID);
  5089.     dsURI = dsURI.replace(/%APP_VERSION%/g, this._updater._appVersion);
  5090.     dsURI = dsURI.replace(/%REQ_VERSION%/g, 1);
  5091.     dsURI = dsURI.replace(/%APP_OS%/g, gOSTarget);
  5092.     dsURI = dsURI.replace(/%APP_ABI%/g, gXPCOMABI);
  5093.     
  5094.     // escape() does not properly encode + symbols in any embedded FVF strings.
  5095.     dsURI = dsURI.replace(/\+/g, "%2B");
  5096.  
  5097.     // Verify that the URI provided is valid
  5098.     try {
  5099.       var uri = newURI(dsURI);
  5100.     }
  5101.     catch (e) {
  5102.       LOG("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" + 
  5103.           " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e);
  5104.       this._updater.checkForDone(aItem, 
  5105.                                  nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5106.       return;
  5107.     }
  5108.  
  5109.     LOG("RDFItemUpdater:checkForUpdates sending a request to server for: " + 
  5110.         uri.spec + ", item = " + aItem.objectSource);        
  5111.  
  5112.     var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  5113.                             .createInstance(Components.interfaces.nsIXMLHttpRequest);
  5114.     request.open("GET", uri.spec, true);
  5115.     request.overrideMimeType("text/xml");
  5116.     request.setRequestHeader("Cache-Control", "no-cache");
  5117.  
  5118.     var self = this;
  5119.     request.onerror     = function(event) { self.onXMLError(event, aItem);    };
  5120.     request.onload      = function(event) { self.onXMLLoad(event, aItem);     };
  5121.     request.send(null);
  5122.   },
  5123.  
  5124.   onXMLLoad: function(aEvent, aItem) {
  5125.     var request = aEvent.target;
  5126.     var responseXML = request.responseXML;
  5127.     if (responseXML)
  5128.       var parseError = (responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR);
  5129.  
  5130.     // If AMO does not return responseXML it is not treated as a failure since
  5131.     // items without an updateURL are checked on AMO. If there is an XML parse
  5132.     // error, responseXML is null, status does NOT equal 200 or 0 (e.g. 200 is
  5133.     // HTTP OK and 0 is returned for a local file) then we don't have valid data.
  5134.     if (!responseXML || parseError || (request.status != 200 && request.status != 0)) {
  5135.       // If the item does not have an updateRDF then the error is from UMO.
  5136.       if (!aItem.updateRDF) {
  5137.         this._updater.checkForDone(aItem, 
  5138.                                    nsIAddonUpdateCheckListener.STATUS_NONE);
  5139.       }
  5140.       else {
  5141.         this._updater.checkForDone(aItem, 
  5142.                                    nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5143.       }
  5144.  
  5145.       return;
  5146.     }
  5147.  
  5148.     var rdfParser = Components.classes["@mozilla.org/rdf/xml-parser;1"]
  5149.                               .createInstance(Components.interfaces.nsIRDFXMLParser)
  5150.     var ds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
  5151.                        .createInstance(Components.interfaces.nsIRDFDataSource);
  5152.     rdfParser.parseString(ds, request.channel.URI, request.responseText);
  5153.  
  5154.     this.onDatasourceLoaded(ds, aItem);
  5155.   },
  5156.  
  5157.   /**
  5158.    *
  5159.    */
  5160.   onXMLError: function(aEvent, aItem) {
  5161.     try {
  5162.       var request = aEvent.target;
  5163.       // the following may throw (e.g. a local file or timeout)
  5164.       var status = request.status;
  5165.     }
  5166.     catch (e) {
  5167.       request = aEvent.target.channel.QueryInterface(Components.interfaces.nsIRequest);
  5168.       status = request.status;
  5169.     }
  5170.  
  5171.     var statusText = request.statusText;
  5172.  
  5173.     // When status is 0 we don't have a valid channel.
  5174.     if (status == 0)
  5175.       statusText = "nsIXMLHttpRequest channel unavailable";
  5176.  
  5177.     LOG("RDFItemUpdater:onError: There was an error loading the \r\n" + 
  5178.         "the update datasource for item " + aItem.id + ", error: " + statusText);
  5179.     this._updater.checkForDone(aItem, 
  5180.                                nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5181.   },
  5182.  
  5183.   onDatasourceLoaded: function(aDatasource, aLocalItem) {
  5184.     ///////////////////////////////////////////////////////////////////////////    
  5185.     // The extension update RDF file looks something like this:
  5186.     //
  5187.     //  <RDF:Description about="urn:mozilla:extension:{GUID}">
  5188.     //    <em:updates>
  5189.     //      <RDF:Seq>
  5190.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:4.9"/>
  5191.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:5.0"/>
  5192.     //      </RDF:Seq>
  5193.     //    </em:updates>
  5194.     //    <!-- the version of the extension being offered -->
  5195.     //    <em:version>5.0</em:version>
  5196.     //    <em:updateLink>http://www.mysite.com/myext-50.xpi</em:updateLink>
  5197.     //  </RDF:Description>
  5198.     //
  5199.     //  <RDF:Description about="urn:mozilla:extension:{GUID}:4.9">
  5200.     //    <em:version>4.9</em:version>
  5201.     //    <em:targetApplication>
  5202.     //      <RDF:Description>
  5203.     //        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
  5204.     //        <em:minVersion>0.9</em:minVersion>
  5205.     //        <em:maxVersion>1.0</em:maxVersion>
  5206.     //        <em:updateLink>http://www.mysite.com/myext-49.xpi</em:updateLink>
  5207.     //      </RDF:Description>
  5208.     //    </em:targetApplication>
  5209.     //  </RDF:Description>  
  5210.     //
  5211.     // If we get here because the following happened:
  5212.     // 1) User was using Firefox 0.9 with ExtensionX 0.5 (minVersion 0.8, 
  5213.     //    maxVersion 0.9 for Firefox)
  5214.     // 2) User upgraded Firefox to 1.0
  5215.     // 3) |checkForMismatches| deems ExtensionX 0.5 incompatible with this
  5216.     //    new version of Firefox on the basis of its maxVersion
  5217.     // 4) ** We reach this point **
  5218.     //
  5219.     // If the version of ExtensionX (0.5) matches that provided by the 
  5220.     // server, then this is a cue that the author updated the rdf file
  5221.     // or central repository to say "0.5 is ALSO compatible with Firefox 1.0,
  5222.     // no changes are necessary." In this event, the local metadata for
  5223.     // installed ExtensionX (0.5) is freshened with the new maxVersion, 
  5224.     // and we advance to the next item WITHOUT any download/install 
  5225.     // updates.
  5226.     if (!aDatasource.GetAllResources().hasMoreElements()) {
  5227.       LOG("RDFItemUpdater:onDatasourceLoaded: Datasource empty.\r\n" + 
  5228.           "If you are an Extension developer and were expecting there to be\r\n" + 
  5229.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  5230.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  5231.           "\r\nTry checking that: \r\n" + 
  5232.           " 1. Your remote RDF file exists at the location.\r\n" + 
  5233.           " 2. Your RDF file is valid XML (starts with <?xml version=\"1.0?\">\r\n" + 
  5234.           "    and loads in Firefox displaying pretty printed like other XML documents\r\n" + 
  5235.           " 3. Your server is sending the data in the correct MIME\r\n" + 
  5236.           "    type (text/xml)");
  5237.     }      
  5238.     
  5239.   
  5240.     // Parse the response RDF
  5241.     function UpdateData() {}; 
  5242.     UpdateData.prototype = { version: "0.0", updateLink: null, updateHash: null,
  5243.                              minVersion: "0.0", maxVersion: "0.0" };
  5244.     
  5245.     var versionUpdate = new UpdateData();
  5246.     var newestUpdate  = new UpdateData();
  5247.  
  5248.     var newerItem, sameItem;
  5249.     
  5250.     // Firefox 1.0PR+ update.rdf format
  5251.     if (!this._versionUpdateOnly) {
  5252.       // Look for newer versions of this item, we only do this in "normal" 
  5253.       // mode... see comment by ExtensionItemUpdater_checkForUpdates 
  5254.       // about how we do this in all cases but Install Phone Home - which 
  5255.       // only needs to do a version check.
  5256.       this._parseV20UpdateInfo(aDatasource, aLocalItem, newestUpdate, false);
  5257.       if (!newestUpdate.updateLink) {
  5258.         // Firefox 0.9 update.rdf format - does not contain any metadata
  5259.         // that can be used for version updates, so performed in the "all updates"
  5260.         // mode only. 
  5261.         this._parseV10UpdateInfo(aDatasource, aLocalItem, newestUpdate);
  5262.       }
  5263.  
  5264.       newerItem = makeItem(aLocalItem.id, 
  5265.                            newestUpdate.version, 
  5266.                            aLocalItem.installLocationKey,
  5267.                            newestUpdate.minVersion, 
  5268.                            newestUpdate.maxVersion, 
  5269.                            aLocalItem.name, 
  5270.                            newestUpdate.updateLink,
  5271.                            newestUpdate.updateHash,
  5272.                            "", /* Icon URL */
  5273.                            "", /* RDF Update URL */
  5274.                            aLocalItem.type);
  5275.       if (this._updater._isValidUpdate(aLocalItem, newerItem))
  5276.         ++this._updater._updateCount;
  5277.       else
  5278.         newerItem = null;
  5279.     }
  5280.     
  5281.     // Now look for updated version compatibility metadata for the currently
  5282.     // installed version...
  5283.     this._parseV20UpdateInfo(aDatasource, aLocalItem, versionUpdate, true);
  5284.  
  5285.     var result = gVersionChecker.compare(versionUpdate.version, 
  5286.                                           aLocalItem.version);
  5287.     if (result == 0) {
  5288.       // Local version exactly matches the "Version Update" remote version, 
  5289.       // Apply changes into local datasource.
  5290.       sameItem = makeItem(aLocalItem.id, 
  5291.                           versionUpdate.version, 
  5292.                           aLocalItem.installLocationKey,
  5293.                           versionUpdate.minVersion, 
  5294.                           versionUpdate.maxVersion, 
  5295.                           aLocalItem.name,
  5296.                           "", /* XPI Update URL */
  5297.                           "", /* XPI Update Hash */
  5298.                           "", /* Icon URL */
  5299.                           "", /* RDF Update URL */
  5300.                           aLocalItem.type);
  5301.       if (this._updater._isValidUpdate(aLocalItem, sameItem)) {
  5302.         // Install-time updates are not written to the DS because there is no
  5303.         // entry yet, EM just uses the notifications to ascertain (by hand)
  5304.         // whether or not there is a remote maxVersion tweak that makes the 
  5305.         // item being installed compatible.
  5306.         if (!this._updater._applyVersionUpdates(aLocalItem, sameItem))
  5307.           sameItem = null;
  5308.       }
  5309.       else 
  5310.         sameItem = null;
  5311.     }
  5312.     
  5313.     if (newerItem) {
  5314.       LOG("RDFItemUpdater:onDatasourceLoaded: Found a newer version of this item:\r\n" + 
  5315.           newerItem.objectSource);
  5316.     }
  5317.     if (sameItem) {
  5318.       LOG("RDFItemUpdater:onDatasourceLoaded: Found info about the installed\r\n" + 
  5319.           "version of this item: " + sameItem.objectSource);
  5320.     }
  5321.     var item = null, status = nsIAddonUpdateCheckListener.STATUS_NONE;
  5322.     if (!this._versionUpdateOnly && newerItem) {
  5323.       item = newerItem;
  5324.       status = nsIAddonUpdateCheckListener.STATUS_UPDATE;
  5325.     }
  5326.     else if (sameItem) {
  5327.       item = sameItem;
  5328.       status = nsIAddonUpdateCheckListener.STATUS_VERSIONINFO;
  5329.     }
  5330.     else {
  5331.       item = aLocalItem;
  5332.       status = nsIAddonUpdateCheckListener.STATUS_NO_UPDATE;
  5333.     }
  5334.     // Only one call of this._updater.checkForDone is needed for RDF 
  5335.     // responses, since there is only one response per item.
  5336.     this._updater.checkForDone(item, status);
  5337.   },
  5338.  
  5339.   // Parses Firefox 0.9 update.rdf format  
  5340.   _parseV10UpdateInfo: function(aDataSource, aLocalItem, aUpdateData) {
  5341.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  5342.     
  5343.     aUpdateData.version     = this._getPropertyFromResource(aDataSource, extensionRes, 
  5344.                                                             "version", aLocalItem);
  5345.     aUpdateData.updateLink  = this._getPropertyFromResource(aDataSource, extensionRes, 
  5346.                                                             "updateLink", aLocalItem);
  5347.   },
  5348.   
  5349.   // Get a compulsory property from a resource. Reports an error if the 
  5350.   // property was not present. 
  5351.   _getPropertyFromResource: function(aDataSource, aSourceResource, aProperty, aLocalItem) {
  5352.     var rv;
  5353.     try {
  5354.       var property = gRDF.GetResource(EM_NS(aProperty));
  5355.       rv = stringData(aDataSource.GetTarget(aSourceResource, property, true));
  5356.       if (rv === undefined)
  5357.         throw Components.results.NS_ERROR_FAILURE;
  5358.     }
  5359.     catch (e) {
  5360.       // XXXben show console message "aProperty" not found on aSourceResource. 
  5361.       return null;
  5362.     }
  5363.     return rv;
  5364.   },
  5365.   
  5366.   // Parses Firefox 1.0RC1+ update.rdf format
  5367.   _parseV20UpdateInfo: function(aDataSource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  5368.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  5369.  
  5370.     var updatesArc = gRDF.GetResource(EM_NS("updates"));
  5371.     var updates = aDataSource.GetTarget(extensionRes, updatesArc, true);
  5372.     
  5373.     try {
  5374.       updates = updates.QueryInterface(Components.interfaces.nsIRDFResource);
  5375.     }
  5376.     catch (e) { 
  5377.       LOG("RDFItemUpdater:_parseV20UpdateInfo: No updates were found for:\r\n" + 
  5378.           aLocalItem.id + "\r\n" + 
  5379.           "If you are an Extension developer and were expecting there to be\r\n" + 
  5380.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  5381.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  5382.           "\r\nTry checking that: \r\n" + 
  5383.           " 1. Your RDF File is correct - e.g. check that there is a top level\r\n" + 
  5384.           "    RDF Resource with a URI urn:mozilla:extension:{GUID}, and that\r\n" + 
  5385.           "    the <em:updates> listed all have matching GUIDs.");
  5386.       return; 
  5387.     }
  5388.     
  5389.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  5390.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  5391.     if (cu.IsContainer(aDataSource, updates)) {
  5392.       var ctr = getContainer(aDataSource, updates);
  5393.  
  5394.       // In "all update types" mode, we look for newer versions, starting with the 
  5395.       // current installed version.
  5396.       if (!aVersionUpdatesOnly) 
  5397.         aUpdateData.version = aLocalItem.version;
  5398.  
  5399.       var versions = ctr.GetElements();
  5400.       while (versions.hasMoreElements()) {
  5401.         // There are two different methodologies for collecting version 
  5402.         // information depending on whether or not we've bene invoked in 
  5403.         // "version updates only" mode or "version+newest" mode. 
  5404.         var version = versions.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5405.         this._parseV20Update(aDataSource, version, aLocalItem, aUpdateData, aVersionUpdatesOnly);
  5406.         if (aVersionUpdatesOnly && aUpdateData.updateLink)
  5407.           break;
  5408.       }
  5409.     }
  5410.   },
  5411.   
  5412.   _parseV20Update: function(aDataSource, aUpdateResource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  5413.     var version = this._getPropertyFromResource(aDataSource, aUpdateResource, 
  5414.                                                 "version", aLocalItem);
  5415.     var taArc = gRDF.GetResource(EM_NS("targetApplication"));
  5416.     var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true);
  5417.     while (targetApps.hasMoreElements()) {
  5418.       var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5419.       var id = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem);
  5420.       if (id != this._updater._appID)
  5421.         continue;
  5422.       
  5423.       var result = gVersionChecker.compare(version, aLocalItem.version);
  5424.       if (aVersionUpdatesOnly ? result == 0 : result > 0) {
  5425.         aUpdateData.version = version;
  5426.         aUpdateData.updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem);
  5427.         aUpdateData.updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem);
  5428.         aUpdateData.minVersion = this._getPropertyFromResource(aDataSource, targetApp, "minVersion", aLocalItem);
  5429.         aUpdateData.maxVersion = this._getPropertyFromResource(aDataSource, targetApp, "maxVersion", aLocalItem);
  5430.       }
  5431.     }
  5432.   }
  5433. };
  5434.  
  5435. /**
  5436.  * A Datasource that holds Extensions. 
  5437.  * - Implements nsIRDFDataSource to drive UI
  5438.  * - Uses a RDF/XML datasource for storage (this is undesirable)
  5439.  * 
  5440.  * @constructor
  5441.  */
  5442. function ExtensionsDataSource(em) {
  5443.   this._em = em;
  5444.   
  5445.   this._itemRoot = gRDF.GetResource(RDFURI_ITEM_ROOT);
  5446.   this._defaultTheme = gRDF.GetResource(RDFURI_DEFAULT_THEME);
  5447.   gRDF.RegisterDataSource(this, true);
  5448. }
  5449. ExtensionsDataSource.prototype = {
  5450.   _inner    : null,
  5451.   _em       : null,
  5452.   _itemRoot     : null,
  5453.   _defaultTheme : null,
  5454.   
  5455.   /**
  5456.    * Determine if an item is compatible
  5457.    * @param   datasource
  5458.    *          The datasource to inspect for compatibility - can be the main
  5459.    *          datasource or an Install Manifest.
  5460.    * @param   source
  5461.    *          The RDF Resource of the item to inspect for compatibility.
  5462.    * @param   version
  5463.    *          The version of the application we are checking for compatibility
  5464.    *          against. If this parameter is undefined, the version of the running
  5465.    *          application is used.
  5466.    * @returns true if the item is compatible with this version of the 
  5467.    *          application, false, otherwise.
  5468.    */
  5469.   isCompatible: function (datasource, source, version) {
  5470.     // The Default Theme is always compatible. 
  5471.     if (source.EqualsNode(this._defaultTheme))
  5472.       return true;
  5473.  
  5474.     if (version === undefined) {
  5475.       version = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  5476.                         gApp.version);
  5477.     }              
  5478.     var appID = gApp.ID;
  5479.     
  5480.     var targets = datasource.GetTargets(source, EM_R("targetApplication"), true);
  5481.     var idRes = EM_R("id");
  5482.     var minVersionRes = EM_R("minVersion");
  5483.     var maxVersionRes = EM_R("maxVersion");
  5484.     while (targets.hasMoreElements()) {
  5485.       var targetApp = targets.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5486.       var id          = stringData(datasource.GetTarget(targetApp, idRes, true));
  5487.       var minVersion  = stringData(datasource.GetTarget(targetApp, minVersionRes, true));
  5488.       var maxVersion  = stringData(datasource.GetTarget(targetApp, maxVersionRes, true));
  5489.       if (id == appID) {
  5490.         var versionChecker = getVersionChecker();
  5491.         return ((versionChecker.compare(version, minVersion) >= 0) &&
  5492.                 (versionChecker.compare(version, maxVersion) <= 0));
  5493.       }
  5494.     }
  5495.     return false;
  5496.   },
  5497.   
  5498.   /**
  5499.    * Gets a list of items that are incompatible with a specific application version.
  5500.    * @param   appID
  5501.    *          The ID of the application - XXXben unused?
  5502.    * @param   appVersion
  5503.    *          The Version of the application to check for incompatibility against.
  5504.    * @param   desiredType
  5505.    *          The nsIUpdateItem type of items to look for
  5506.    * @param   includeDisabled
  5507.    *          Whether or not disabled items should be included in the set returned
  5508.    * @returns An array of nsIUpdateItems that are incompatible with the application
  5509.    *          ID/Version supplied.
  5510.    */
  5511.   getIncompatibleItemList: function(appID, appVersion, desiredType, includeDisabled) {
  5512.     var items = [];
  5513.     var ctr = getContainer(this._inner, this._itemRoot);
  5514.     var elements = ctr.GetElements();
  5515.     while (elements.hasMoreElements()) {
  5516.       var item = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5517.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  5518.       var type = this.getItemProperty(id, "type");
  5519.       // Skip this item if we're not seeking disabled items
  5520.       if (!includeDisabled &&
  5521.          (this.getItemProperty(id, "disabled") == "true" ||
  5522.           this.getItemProperty(id, "appDisabled") == "true"))
  5523.         continue;
  5524.       
  5525.       // If the id of this item matches one of the items potentially installed
  5526.       // with and maintained by this application AND it is installed in the 
  5527.       // global install location (i.e. the place installed by the app installer)
  5528.       // it is and can be managed by the update file - it's not an item that has
  5529.       // been manually installed by the user into their profile dir, and as such
  5530.       // it is always compatible with the next release of the application since
  5531.       // we will continue to support it.
  5532.       var locationKey = this.getItemProperty(id, "installLocation");
  5533.       var appManaged = this.getItemProperty(id, "appManaged") == "true";
  5534.       if (appManaged && locationKey == KEY_APP_GLOBAL)
  5535.         continue;
  5536.  
  5537.       if (type != -1 && (type & desiredType) && 
  5538.           !this.isCompatible(this, item, appVersion))
  5539.         items.push(this.getItemForID(id));
  5540.     }
  5541.     return items;
  5542.   },
  5543.   
  5544.   /**
  5545.    * Gets a list of items of a specific type
  5546.    * @param   desiredType
  5547.    *          The nsIUpdateItem type of items to return
  5548.    * @param   countRef
  5549.    *          The XPCJS reference to the size of the returned array
  5550.    * @returns An array of nsIUpdateItems, populated only with an item for |id|
  5551.    *          if |id| is non-null, otherwise all items matching the specified
  5552.    *          type.
  5553.    */
  5554.   getItemList: function(desiredType, countRef) {
  5555.     var items = [];
  5556.     var ctr = getContainer(this, this._itemRoot);      
  5557.     var elements = ctr.GetElements();
  5558.     while (elements.hasMoreElements()) {
  5559.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5560.       var eID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  5561.       var type = this.getItemProperty(eID, "type");
  5562.       if (type != -1 && type & desiredType)
  5563.         items.push(this.getItemForID(eID));
  5564.     }
  5565.     countRef.value = items.length;
  5566.     return items;
  5567.   },
  5568.  
  5569.   /**
  5570.    * Get a list of Item IDs that have a flag set
  5571.    * @param   flag
  5572.    *          The name of an RDF property (less EM_NS) to check for
  5573.    * @param   desiredType
  5574.    *          The nsIUpdateItem type of item to look for
  5575.    * @returns An array of Item IDs 
  5576.    *
  5577.    * XXXben - this function is a little weird since it returns an array of 
  5578.    *          strings, not an array of nsIUpdateItems...  
  5579.    */
  5580.   getItemsWithFlagUnset: function(flag, desiredType) {
  5581.     var items = [];
  5582.  
  5583.     var ctr = getContainer(this, this._itemRoot);    
  5584.     var elements = ctr.GetElements();
  5585.     while (elements.hasMoreElements()) {
  5586.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5587.       var id = stripPrefix(e.Value, PREFIX_ITEM_URI);
  5588.       var type = this.getItemProperty(id, "type");
  5589.       if (type != -1 && type & desiredType) {
  5590.         var value = this.GetTarget(e, EM_R(flag), true);
  5591.         if (!value)
  5592.           items.push(id);
  5593.       }
  5594.     }
  5595.     return items;
  5596.   },
  5597.   
  5598.   /**
  5599.    * Constructs an nsIUpdateItem for the given item ID
  5600.    * @param   id
  5601.    *          The GUID of the item to construct a nsIUpdateItem for
  5602.    * @returns The nsIUpdateItem for the id.
  5603.    */  
  5604.   getItemForID: function(id) {
  5605.     var r = getResourceForID(id);
  5606.     if (!r)
  5607.       return null;
  5608.     
  5609.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  5610.     var updateHash = this.getItemProperty(id, "availableUpdateHash");
  5611.     return makeItem(id, 
  5612.                     this.getItemProperty(id, "version"), 
  5613.                     this.getItemProperty(id, "installLocation"),
  5614.                     targetAppInfo ? targetAppInfo.minVersion : "",
  5615.                     targetAppInfo ? targetAppInfo.maxVersion : "",
  5616.                     this.getItemProperty(id, "name"),
  5617.                     this.getItemProperty(id, "availableUpdateURL"),
  5618.                     updateHash ? updateHash : "",
  5619.                     this.getItemProperty(id, "iconURL"), 
  5620.                     this.getItemProperty(id, "updateURL"), 
  5621.                     this.getItemProperty(id, "type"));
  5622.   },
  5623.   
  5624.   /**
  5625.    * Gets the name of the Install Location where an item is installed.
  5626.    * @param   id
  5627.    *          The GUID of the item to locate an Install Location for
  5628.    * @returns The string name of the Install Location where the item is 
  5629.    *          installed.
  5630.    */
  5631.   getInstallLocationKey: function(id) {
  5632.     return this.getItemProperty(id, "installLocation");
  5633.   },
  5634.   
  5635.   /**
  5636.    * Sets an RDF property on an item in a datasource. Does not create
  5637.    * multiple assertions
  5638.    * @param   datasource
  5639.    *          The target datasource where the property should be set
  5640.    * @param   source
  5641.    *          The RDF Resource to set the property on
  5642.    * @param   property
  5643.    *          The RDF Resource of the property to set
  5644.    * @param   newValue
  5645.    *          The RDF Node containing the new property value
  5646.    */
  5647.   _setProperty: function(datasource, source, property, newValue) {
  5648.     var oldValue = datasource.GetTarget(source, property, true);
  5649.     if (oldValue) {
  5650.       if (newValue)
  5651.         datasource.Change(source, property, oldValue, newValue);
  5652.       else
  5653.         datasource.Unassert(source, property, oldValue);
  5654.     }
  5655.     else if (newValue)
  5656.       datasource.Assert(source, property, newValue, true);
  5657.   },
  5658.   
  5659.   /**
  5660.    * Sets the target application info for an item in the Extensions
  5661.    * datasource and in the item's install manifest if it is installed in a
  5662.    * profile's extensions directory, it exists, and we have write access.
  5663.    * @param   id
  5664.    *          The ID of the item to update target application info for
  5665.    * @param   minVersion
  5666.    *          The minimum version of the target application that this item can
  5667.    *          run in
  5668.    * @param   maxVersion
  5669.    *          The maximum version of the target application that this item can
  5670.    *          run in
  5671.    */
  5672.   updateTargetAppInfo: function(id, minVersion, maxVersion)
  5673.   {
  5674.     // Update the Extensions datasource
  5675.     this.setTargetApplicationInfo(id, minVersion, maxVersion, null);
  5676.  
  5677.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  5678.     if (installLocation.name != KEY_APP_PROFILE)
  5679.       return;
  5680.  
  5681.     var installManifestFile = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  5682.     // Only update if the item exists and we can write to the location
  5683.     if (installManifestFile.exists() && installLocation.canAccess)
  5684.       this.setTargetApplicationInfo(id, minVersion, maxVersion,
  5685.                                     getInstallManifest(installManifestFile));
  5686.   },
  5687.  
  5688.   /**
  5689.    * Gets the updated target application info if it exists for an item from
  5690.    * the Extensions datasource during an installation or upgrade.
  5691.    * @param   id
  5692.    *          The ID of the item to discover updated target application info for
  5693.    * @returns A JS Object with the following properties:
  5694.    *          "id"            The id of the item
  5695.    *          "minVersion"    The updated minimum version of the target
  5696.    *                          application that this item can run in
  5697.    *          "maxVersion"    The updated maximum version of the target
  5698.    *                          application that this item can run in
  5699.    */
  5700.   getUpdatedTargetAppInfo: function(id) {
  5701.     // The default theme is always compatible so there is never update info.
  5702.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  5703.       return null;
  5704.  
  5705.     var appID = gApp.ID;
  5706.     var r = getResourceForID(id);
  5707.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  5708.     if (!targetApps.hasMoreElements())
  5709.       targetApps = this._inner.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  5710.     while (targetApps.hasMoreElements()) {
  5711.       var targetApp = targetApps.getNext();
  5712.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5713.         try {
  5714.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  5715.           if (foundAppID != appID) // Different target application
  5716.             continue;
  5717.           var updatedMinVersion = this._inner.GetTarget(targetApp, EM_R("updatedMinVersion"), true);
  5718.           var updatedMaxVersion = this._inner.GetTarget(targetApp, EM_R("updatedMaxVersion"), true);
  5719.           if (updatedMinVersion && updatedMaxVersion)
  5720.             return { id        : id,
  5721.                      minVersion: stringData(updatedMinVersion),
  5722.                      maxVersion: stringData(updatedMaxVersion) };
  5723.           else
  5724.             return null;
  5725.         }
  5726.         catch (e) { 
  5727.           continue;
  5728.         }
  5729.       }
  5730.     }
  5731.     return null;
  5732.   },
  5733.   
  5734.   /**
  5735.    * Sets the updated target application info for an item in the Extensions
  5736.    * datasource during an installation or upgrade.
  5737.    * @param   id
  5738.    *          The ID of the item to set updated target application info for
  5739.    * @param   updatedMinVersion
  5740.    *          The updated minimum version of the target application that this
  5741.    *          item can run in
  5742.    * @param   updatedMaxVersion
  5743.    *          The updated maximum version of the target application that this
  5744.    *          item can run in
  5745.    */
  5746.   setUpdatedTargetAppInfo: function(id, updatedMinVersion, updatedMaxVersion) {
  5747.     // The default theme is always compatible so it is never updated.
  5748.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  5749.       return;
  5750.  
  5751.     // Version/Dependency Info
  5752.     var updatedMinVersionRes = EM_R("updatedMinVersion");
  5753.     var updatedMaxVersionRes = EM_R("updatedMaxVersion");
  5754.  
  5755.     var appID = gApp.ID;
  5756.     var r = getResourceForID(id);
  5757.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  5758.     // add updatedMinVersion and updatedMaxVersion for an install else an upgrade
  5759.     if (!targetApps.hasMoreElements()) {
  5760.       var idRes = EM_R("id");
  5761.       var targetRes = getResourceForID(id);
  5762.       var property = EM_R("targetApplication");
  5763.       var anon = gRDF.GetAnonymousResource();
  5764.       this._inner.Assert(anon, idRes, EM_L(appID), true);
  5765.       this._inner.Assert(anon, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  5766.       this._inner.Assert(anon, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  5767.       this._inner.Assert(targetRes, property, anon, true);
  5768.     }
  5769.     else {
  5770.       while (targetApps.hasMoreElements()) {
  5771.         var targetApp = targetApps.getNext();
  5772.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5773.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  5774.           if (foundAppID != appID) // Different target application
  5775.             continue;
  5776.           this._inner.Assert(targetApp, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  5777.           this._inner.Assert(targetApp, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  5778.           break;
  5779.         }
  5780.       }
  5781.     }
  5782.     this.Flush();
  5783.   },
  5784.  
  5785.   /**
  5786.    * Gets the target application info for an item from a datasource.
  5787.    * @param   id
  5788.    *          The GUID of the item to discover target application info for
  5789.    * @param   datasource
  5790.    *          The datasource to look up target application info in
  5791.    * @returns A JS Object with the following properties:
  5792.    *          "minVersion"    The minimum version of the target application
  5793.    *                          that this item can run in
  5794.    *          "maxVersion"    The maximum version of the target application
  5795.    *                          that this item can run in
  5796.    *          or null, if no target application data exists for the specified
  5797.    *          id in the supplied datasource.
  5798.    */
  5799.   getTargetApplicationInfo: function(id, datasource) {
  5800.     // The default theme is always compatible. 
  5801.     if (getResourceForID(id).EqualsNode(this._defaultTheme)) {
  5802.       var ver = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  5803.                         gApp.version);
  5804.       return { minVersion: ver, maxVersion: ver };
  5805.     }
  5806.     var appID = gApp.ID;
  5807.     var r = getResourceForID(id);
  5808.     var targetApps = datasource.GetTargets(r, EM_R("targetApplication"), true);
  5809.     if (!targetApps)
  5810.       return null;
  5811.     if (!targetApps.hasMoreElements())
  5812.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  5813.     while (targetApps.hasMoreElements()) {
  5814.       var targetApp = targetApps.getNext();
  5815.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5816.         try {
  5817.           var foundAppID = stringData(datasource.GetTarget(targetApp, EM_R("id"), true));
  5818.           if (foundAppID != appID) // Different target application
  5819.             continue;
  5820.           
  5821.           return { minVersion: stringData(datasource.GetTarget(targetApp, EM_R("minVersion"), true)),
  5822.                    maxVersion: stringData(datasource.GetTarget(targetApp, EM_R("maxVersion"), true)) };
  5823.         }
  5824.         catch (e) { 
  5825.           continue;
  5826.         }
  5827.       }
  5828.     }
  5829.     return null;
  5830.   },
  5831.   
  5832.   /**
  5833.    * Sets the target application info for an item in a datasource.
  5834.    * @param   id
  5835.    *          The GUID of the item to discover target application info for
  5836.    * @param   minVersion
  5837.    *          The minimum version of the target application that this item can
  5838.    *          run in
  5839.    * @param   maxVersion
  5840.    *          The maximum version of the target application that this item can
  5841.    *          run in
  5842.    * @param   datasource
  5843.    *          The datasource to loko up target application info in
  5844.    */
  5845.   setTargetApplicationInfo: function(id, minVersion, maxVersion, datasource) {
  5846.     var targetDataSource = datasource;
  5847.     if (!targetDataSource)
  5848.       targetDataSource = this._inner;
  5849.       
  5850.     var appID = gApp.ID;
  5851.     var r = getResourceForID(id);
  5852.     var targetApps = targetDataSource.GetTargets(r, EM_R("targetApplication"), true);
  5853.     if (!targetApps.hasMoreElements())
  5854.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  5855.     while (targetApps.hasMoreElements()) {
  5856.       var targetApp = targetApps.getNext();
  5857.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5858.         var foundAppID = stringData(targetDataSource.GetTarget(targetApp, EM_R("id"), true));
  5859.         if (foundAppID != appID) // Different target application
  5860.           continue;
  5861.         
  5862.         this._setProperty(targetDataSource, targetApp, EM_R("minVersion"), EM_L(minVersion));
  5863.         this._setProperty(targetDataSource, targetApp, EM_R("maxVersion"), EM_L(maxVersion));
  5864.         
  5865.         // If we were setting these properties on the main datasource, flush
  5866.         // it now. (Don't flush changes set on Install Manifests - they are
  5867.         // fleeting).
  5868.         if (!datasource)
  5869.           this.Flush();
  5870.  
  5871.         break;
  5872.       }
  5873.     }
  5874.   },
  5875.   
  5876.   /** 
  5877.    * Gets a property of an item
  5878.    * @param   id
  5879.    *          The GUID of the item
  5880.    * @param   property
  5881.    *          The name of the property (excluding EM_NS)
  5882.    * @returns The literal value of the property, or undefined if there is no 
  5883.    *          value.
  5884.    */
  5885.   getItemProperty: function(id, property) { 
  5886.     var item = getResourceForID(id);
  5887.     if (!item) {
  5888.       LOG("getItemProperty failing for lack of an item. This means getResourceForItem \
  5889.            failed to locate a resource for aItemID (item ID = " + id + ", property = " + property + ")");
  5890.     }
  5891.     else 
  5892.       return this._getItemProperty(item, property);
  5893.     return undefined;
  5894.   },
  5895.   
  5896.   /**
  5897.    * Gets a property of an item resource
  5898.    * @param   itemResource
  5899.    *          The RDF Resource of the item
  5900.    * @param   property
  5901.    *          The name of the property (excluding EM_NS)
  5902.    * @returns The literal value of the property, or undefined if there is no
  5903.    *          value.
  5904.    */
  5905.   _getItemProperty: function(itemResource, property) {
  5906.     var target = this.GetTarget(itemResource, EM_R(property), true);
  5907.     var value = stringData(target);
  5908.     if (value === undefined)
  5909.       value = intData(target);
  5910.     return value === undefined ? "" : value;
  5911.   },
  5912.   
  5913.   /**
  5914.    * Sets a property on an item.
  5915.    * @param   id
  5916.    *          The GUID of the item
  5917.    * @param   propertyArc
  5918.    *          The RDF Resource of the property arc
  5919.    * @param   propertyValue
  5920.    *          A nsIRDFLiteral value of the property to be set
  5921.    */
  5922.   setItemProperty: function (id, propertyArc, propertyValue) {
  5923.     var item = getResourceForID(id);
  5924.     this._setProperty(this._inner, item, propertyArc, propertyValue);
  5925.     this.Flush();  
  5926.   },
  5927.  
  5928.   /**
  5929.    * Inserts the RDF resource for an item into a container.
  5930.    * @param   id
  5931.    *          The GUID of the item
  5932.    */
  5933.   insertItemIntoContainer: function(id) {
  5934.     // Get the target container and resource
  5935.     var ctr = getContainer(this._inner, this._itemRoot);
  5936.     var itemResource = getResourceForID(id);
  5937.     // Don't bother adding the extension to the list if it's already there. 
  5938.     // (i.e. we're upgrading)
  5939.     var oldIndex = ctr.IndexOf(itemResource);
  5940.     if (oldIndex == -1)
  5941.       ctr.AppendElement(itemResource);
  5942.     this.Flush();
  5943.   }, 
  5944.  
  5945.   /**
  5946.    * Removes the RDF resource for an item from its container.
  5947.    * @param   id
  5948.    *          The GUID of the item
  5949.    */
  5950.   removeItemFromContainer: function(id) {
  5951.     var ctr = getContainer(this._inner, this._itemRoot);
  5952.     var itemResource = getResourceForID(id);
  5953.     ctr.RemoveElement(itemResource, true);
  5954.     this.Flush();
  5955.   },
  5956.  
  5957.   /**
  5958.    * Removes a corrupt item entry from the extension list added due to buggy 
  5959.    * code in previous EM versions!  
  5960.    * @param   id
  5961.    *          The GUID of the item
  5962.    */
  5963.   removeCorruptItem: function(id) {
  5964.     this.removeItemMetadata(id);
  5965.     this.removeItemFromContainer(id);
  5966.   },
  5967.  
  5968.   /**
  5969.    * Removes a corrupt download entry from the list
  5970.    * @param   uri
  5971.    *          The RDF URI of the item.
  5972.    * @returns The RDF Resource of the removed entry 
  5973.    */
  5974.   removeCorruptDLItem: function(uri) {
  5975.     var itemResource = gRDF.GetResource(uri);
  5976.     var ctr = getContainer(this._inner, this._itemRoot);
  5977.     if (ctr.IndexOf(itemResource) != -1) {
  5978.       ctr.RemoveElement(itemResource, true);
  5979.       this._cleanResource(itemResource);
  5980.       this.Flush();
  5981.     }
  5982.     return itemResource;
  5983.   },
  5984.   
  5985.   /**
  5986.    * Copies metadata from an Install Manifest Datasource into the Extensions
  5987.    * DataSource.
  5988.    * @param   id
  5989.    *          The GUID of the item
  5990.    * @param   installManifest
  5991.    *          The Install Manifest datasource we are copying from
  5992.    * @param   installLocation
  5993.    *          The Install Location of the item. 
  5994.    */
  5995.   addItemMetadata: function(id, installManifest, installLocation) {
  5996.     // Copy the assertions over from the source datasource. 
  5997.     var targetRes = getResourceForID(id);
  5998.     // Assert properties with single values
  5999.     var singleProps = ["version", "name", "description", "creator", "homepageURL", 
  6000.                        "updateURL", "updateService", "optionsURL", "aboutURL", 
  6001.                        "iconURL", "internalName"];
  6002.  
  6003.     // Items installed into restricted Install Locations can also be locked 
  6004.     // (can't be removed or disabled), and hidden (not shown in the UI)
  6005.     if (installLocation.restricted)
  6006.       singleProps = singleProps.concat(["locked", "hidden"]);
  6007.     if (installLocation.name == KEY_APP_GLOBAL) 
  6008.       singleProps = singleProps.concat(["appManaged"]);
  6009.     for (var i = 0; i < singleProps.length; ++i) {
  6010.       var property = EM_R(singleProps[i]);
  6011.       var literal = installManifest.GetTarget(gInstallManifestRoot, property, true);
  6012.       // If literal is null, _setProperty will remove any existing.
  6013.       this._setProperty(this._inner, targetRes, property, literal);
  6014.     }    
  6015.     
  6016.     // Assert properties with multiple values    
  6017.     var manyProps = ["contributor"];
  6018.     for (var i = 0; i < manyProps.length; ++i) {
  6019.       var property = EM_R(manyProps[i]);
  6020.       var literals = installManifest.GetTargets(gInstallManifestRoot, property, true);
  6021.       
  6022.       var oldValues = this._inner.GetTargets(targetRes, property, true);
  6023.       while (oldValues.hasMoreElements()) {
  6024.         var oldValue = oldValues.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6025.         this._inner.Unassert(targetRes, property, oldValue);
  6026.       }
  6027.       while (literals.hasMoreElements()) {
  6028.         var literal = literals.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6029.         this._inner.Assert(targetRes, property, literal, true);
  6030.       }
  6031.     }
  6032.  
  6033.     // Version/Dependency Info
  6034.     var versionProps = ["targetApplication", "requires"];
  6035.     var idRes = EM_R("id");
  6036.     var minVersionRes = EM_R("minVersion");
  6037.     var maxVersionRes = EM_R("maxVersion");
  6038.     for (var i = 0; i < versionProps.length; ++i) {
  6039.       var property = EM_R(versionProps[i]);
  6040.       var newVersionInfos = installManifest.GetTargets(gInstallManifestRoot, property, true);
  6041.  
  6042.       var oldVersionInfos = this._inner.GetTargets(targetRes, property, true);
  6043.       while (oldVersionInfos.hasMoreElements()) {
  6044.         var oldVersionInfo = oldVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6045.         this._cleanResource(oldVersionInfo);
  6046.         this._inner.Unassert(targetRes, property, oldVersionInfo);
  6047.       }
  6048.       while (newVersionInfos.hasMoreElements()) {
  6049.         var newVersionInfo = newVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6050.         var anon = gRDF.GetAnonymousResource();
  6051.         this._inner.Assert(anon, idRes, installManifest.GetTarget(newVersionInfo, idRes, true), true);
  6052.         this._inner.Assert(anon, minVersionRes, installManifest.GetTarget(newVersionInfo, minVersionRes, true), true);
  6053.         this._inner.Assert(anon, maxVersionRes, installManifest.GetTarget(newVersionInfo, maxVersionRes, true), true);
  6054.         this._inner.Assert(targetRes, property, anon, true);
  6055.       }
  6056.     }
  6057.     this.updateProperty(id, "opType");
  6058.     this.updateProperty(id, "updateable");
  6059.     this.updateProperty(id, "displayDescription");
  6060.     this.Flush();
  6061.   },
  6062.   
  6063.   /**
  6064.    * Strips an item entry of all assertions.
  6065.    * @param   id
  6066.    *          The GUID of the item
  6067.    */
  6068.   removeItemMetadata: function(id) {
  6069.     var item = getResourceForID(id);
  6070.     var resources = ["targetApplication", "requires"];
  6071.     for (var i = 0; i < resources.length; ++i) {
  6072.       var targetApps = this._inner.GetTargets(item, EM_R(resources[i]), true);
  6073.       while (targetApps.hasMoreElements()) {
  6074.         var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6075.         this._cleanResource(targetApp);
  6076.       }
  6077.     }
  6078.  
  6079.     this._cleanResource(item);
  6080.   },
  6081.   
  6082.   /**
  6083.    * Strips a resource of all outbound assertions. We use methods like this 
  6084.    * since the RDFXMLDatasource will write out all assertions, even if they
  6085.    * are not connected through our root. 
  6086.    * @param   resource
  6087.    *          The resource to clean. 
  6088.    */
  6089.   _cleanResource: function(resource) {
  6090.     // Remove outward arcs
  6091.     var arcs = this._inner.ArcLabelsOut(resource);
  6092.     while (arcs.hasMoreElements()) {
  6093.       var arc = arcs.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6094.       var targets = this._inner.GetTargets(resource, arc, true);
  6095.       while (targets.hasMoreElements()) {
  6096.         var value = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6097.         if (value)
  6098.           this._inner.Unassert(resource, arc, value);
  6099.       }
  6100.     }
  6101.   },
  6102.   
  6103.   /**
  6104.    * Notify views that this propery has changed (this is for properties that
  6105.    * are implemented by this datasource rather than by the inner in-memory
  6106.    * datasource and thus do not get free change handling).
  6107.    * @param   id 
  6108.    *          The GUID of the item to update the property for.
  6109.    * @param   property
  6110.    *          The property (less EM_NS) to update.
  6111.    */
  6112.   updateProperty: function(id, property) {
  6113.     var item = getResourceForID(id);
  6114.     var propertyResource = EM_R(property);
  6115.     var value = this.GetTarget(item, propertyResource, true);
  6116.     if (item && value) {
  6117.       for (var i = 0; i < this._observers.length; ++i)
  6118.         this._observers[i].onChange(this, item, propertyResource, 
  6119.                                     EM_L(""), value);
  6120.     }
  6121.   },
  6122.   
  6123.   /**
  6124.    * Move an Item to the index of another item in its container.
  6125.    * @param   movingID
  6126.    *          The ID of the item to be moved.
  6127.    * @param   destinationID
  6128.    *          The ID of an item to move another item to.
  6129.    */
  6130.   moveToIndexOf: function(movingID, destinationID) {
  6131.     var extensions = gRDF.GetResource(RDFURI_ITEM_ROOT);
  6132.     var ctr = getContainer(this._inner, extensions);
  6133.     var item = gRDF.GetResource(movingID);
  6134.     var index = ctr.IndexOf(gRDF.GetResource(destinationID));
  6135.     if (index == -1)
  6136.       index = 1; // move to the beginning if destinationID is not found
  6137.     this._inner.beginUpdateBatch();
  6138.     ctr.RemoveElement(item, true);
  6139.     ctr.InsertElementAt(item, index, true);
  6140.     this._inner.endUpdateBatch();
  6141.     this.Flush();
  6142.   },
  6143.  
  6144.   /**
  6145.    * Determines if an Item is an active download
  6146.    * @param   id
  6147.    *          The GUID of the item
  6148.    * @returns true if the item is an active download, false otherwise.
  6149.    */
  6150.   isDownloadItem: function(id) {
  6151.     return this.getItemProperty(id, "downloadURL") != "";
  6152.   },
  6153.  
  6154.   /**
  6155.    * Adds an entry representing an active download to the appropriate container
  6156.    * @param   addon
  6157.    *          An object implementing nsIUpdateItem for the addon being 
  6158.    *          downloaded.
  6159.    */
  6160.   addDownload: function(addon) {
  6161.     if (addon.id != addon.xpiURL)
  6162.       return;
  6163.     var res = gRDF.GetResource(addon.xpiURL);
  6164.     this._setProperty(this._inner, res, EM_R("name"), EM_L(addon.name));
  6165.     this._setProperty(this._inner, res, EM_R("version"), EM_L(addon.version));
  6166.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(addon.iconURL));
  6167.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(addon.xpiURL));
  6168.     this._setProperty(this._inner, res, EM_R("type"), EM_I(addon.type));
  6169.  
  6170.     var ctr = getContainer(this._inner, this._itemRoot);
  6171.     if (ctr.IndexOf(res) == -1)
  6172.       ctr.AppendElement(res);
  6173.     
  6174.     this.Flush();
  6175.   },
  6176.   
  6177.   /**
  6178.    * Adds an entry representing an item that is incompatible and is being
  6179.    * checked for a compatibility update.
  6180.    * @param   name
  6181.    *          The display name of the item being checked
  6182.    * @param   url
  6183.    *          The URL string of the xpi file that has been staged. This is
  6184.    *          also used for installLocation to make this an independently
  6185.    *          managed item
  6186.    * @param   type
  6187.    *          The nsIUpdateItem type of the item
  6188.    * @param   version
  6189.    *          The version of the item
  6190.    */
  6191.   addIncompatibleUpdateItem: function(name, url, type, version) {
  6192.     // type must be TYPE_EXTENSION for a multi_xpi to display in the manager.
  6193.     if (type == nsIUpdateItem.TYPE_MULTI_XPI)
  6194.       type = nsIUpdateItem.TYPE_EXTENSION;
  6195.  
  6196.     var iconURL = (type == nsIUpdateItem.TYPE_THEME) ? URI_GENERIC_ICON_THEME :
  6197.                                                        URI_GENERIC_ICON_XPINSTALL;
  6198.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6199.     var updateMsg = extensionsStrings.formatStringFromName("incompatibleUpdateMessage",
  6200.                                                            [BundleManager.appName, name], 2)
  6201.  
  6202.     var res = gRDF.GetResource(url);
  6203.     this._setProperty(this._inner, res, EM_R("name"), EM_L(name));
  6204.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(iconURL));
  6205.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(url));
  6206.     this._setProperty(this._inner, res, EM_R("type"), EM_I(type));
  6207.     this._setProperty(this._inner, res, EM_R("version"), EM_L(version));
  6208.     this._setProperty(this._inner, res, EM_R("incompatibleUpdate"), EM_L("true"));
  6209.     this._setProperty(this._inner, res, EM_R("description"), EM_L(updateMsg));
  6210.  
  6211.     var ctr = getContainer(this._inner, this._itemRoot);
  6212.     if (ctr.IndexOf(res) == -1)
  6213.       ctr.AppendElement(res);
  6214.  
  6215.     this.Flush();
  6216.   },
  6217.  
  6218.   /**
  6219.    * Removes an active download from the appropriate container
  6220.    * @param   url
  6221.    *          The URL string of the active download to be removed
  6222.    */
  6223.   removeDownload: function(url) {
  6224.     var res = gRDF.GetResource(url);
  6225.     var ctr = getContainer(this._inner, this._itemRoot);
  6226.     if (ctr.IndexOf(res) != -1) 
  6227.       ctr.RemoveElement(res, true);
  6228.     this._cleanResource(res);
  6229.     this.Flush();
  6230.   },
  6231.   
  6232.   /**
  6233.    * Write download progress info for a set of items to the Datasource
  6234.    * @param   data
  6235.    *          A JS Object containing progress information for a set of active
  6236.    *          downloads, hashed by URL. Each object has the following properties:
  6237.    *          "state"     An integer value representing the download/install
  6238.    *                      state.
  6239.    *          "progress"  An integer value between 0 and 100 representing 
  6240.    *                      percentage complete
  6241.    */
  6242.   flushProgressInfo: function(data) {
  6243.     for (var url in data) {
  6244.       var res = gRDF.GetResource(url);
  6245.       this._setProperty(this._inner, res, EM_R("state"), EM_I(data[url].state));
  6246.       this._setProperty(this._inner, res, EM_R("progress"), EM_I(data[url].progress));
  6247.     }
  6248.     this.Flush();
  6249.   },   
  6250.   
  6251.   /**
  6252.    * A GUID->location-key hash of items that are visible to the application.
  6253.    * These are items that show up in the Extension/Themes etc UI. If there is
  6254.    * an instance of the same item installed in Install Locations of differing 
  6255.    * profiles, the item at the highest priority location will appear in this 
  6256.    * list.
  6257.    */
  6258.   visibleItems: { },
  6259.   
  6260.   /**
  6261.    * Walk the list of installed items and determine what the visible list is, 
  6262.    * based on which items are visible at the highest priority locations. 
  6263.    */  
  6264.   _buildVisibleItemList: function() {
  6265.     var ctr = getContainer(this, this._itemRoot);
  6266.     var items = ctr.GetElements();
  6267.     while (items.hasMoreElements()) {
  6268.       var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6269.       // Resource URIs adopt the format: location-key,item-id
  6270.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6271.       this.visibleItems[id] = this.getItemProperty(id, "installLocation");
  6272.     }
  6273.   },
  6274.   
  6275.   /**
  6276.    * Updates an item's location in the visible item list.
  6277.    * @param   id
  6278.    *          The GUID of the item to update
  6279.    * @param   locationKey
  6280.    *          The name of the Install Location where the item is installed.
  6281.    * @param   forceReplace
  6282.    *          true if the new location should be used, regardless of its 
  6283.    *          priority relationship to existing entries, false if the location
  6284.    *          should only be updated if its priority is lower than the existing
  6285.    *          value.
  6286.    */
  6287.   updateVisibleList: function(id, locationKey, forceReplace) {
  6288.     if (id in this.visibleItems && this.visibleItems[id]) {
  6289.       var oldLocation = InstallLocations.get(this.visibleItems[id]);
  6290.       var newLocation = InstallLocations.get(locationKey);
  6291.       if (forceReplace || newLocation.priority < oldLocation.priority) 
  6292.         this.visibleItems[id] = locationKey;
  6293.     }
  6294.     else 
  6295.       this.visibleItems[id] = locationKey;
  6296.   },
  6297.  
  6298.   /**
  6299.    * Load the Extensions Datasource from disk.
  6300.    */
  6301.   loadExtensions: function() {
  6302.     var extensionsFile  = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  6303.     this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile));
  6304.  
  6305.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  6306.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  6307.     cu.MakeSeq(this._inner, this._itemRoot);
  6308.  
  6309.     this._buildVisibleItemList();
  6310.   },
  6311.   
  6312.   /**
  6313.    * A hash of Addon IDs that we are currently looking for updates to. 
  6314.    */
  6315.   _updateURLs: { },
  6316.   
  6317.   /**
  6318.    * See nsIExtensionManager.idl
  6319.    */
  6320.   onUpdateStarted: function() {
  6321.     LOG("Datasource: Update Started");
  6322.   },
  6323.   
  6324.   /**
  6325.    * See nsIExtensionManager.idl
  6326.    */
  6327.   onUpdateEnded: function() {
  6328.     LOG("Datasource: Update Ended");
  6329.     this._updateURLs = { };
  6330.   },
  6331.   
  6332.   /**
  6333.    * See nsIExtensionManager.idl
  6334.    */
  6335.   onAddonUpdateStarted: function(addon) {
  6336.     LOG("Datasource: Addon Update Started: " + addon.id);
  6337.     this._updateURLs[addon.id] = addon.id;
  6338.     this.updateProperty(addon.id, "availableUpdateURL");
  6339.     this.updateProperty(addon.id, "displayDescription");
  6340.   },
  6341.   
  6342.   /**
  6343.    * See nsIExtensionManager.idl
  6344.    */
  6345.   onAddonUpdateEnded: function(addon, status) {
  6346.     LOG("Datasource: Addon Update Ended: " + addon.id + ", status: " + status);
  6347.     this._updateURLs[addon.id] = status;
  6348.     var url = null, hash = null, version = null;
  6349.     var updateAvailable = status == nsIAddonUpdateCheckListener.STATUS_UPDATE;
  6350.     if (updateAvailable) {
  6351.       url = EM_L(addon.xpiURL);
  6352.       if (addon.xpiHash)
  6353.         hash = EM_L(addon.xpiHash);
  6354.       version = EM_L(addon.version);
  6355.     }
  6356.     this.setItemProperty(addon.id, EM_R("availableUpdateURL"), url);
  6357.     this.setItemProperty(addon.id, EM_R("availableUpdateHash"), hash);
  6358.     this.setItemProperty(addon.id, EM_R("availableUpdateVersion"), version);
  6359.     this.updateProperty(addon.id, "availableUpdateURL");
  6360.     this.updateProperty(addon.id, "displayDescription");
  6361.   },
  6362.  
  6363.   /////////////////////////////////////////////////////////////////////////////
  6364.   // nsIRDFDataSource
  6365.   get URI() {
  6366.     return "rdf:extensions";
  6367.   },
  6368.   
  6369.   GetSource: function(property, target, truthValue) {
  6370.     return this._inner.GetSource(property, target, truthValue);
  6371.   },
  6372.   
  6373.   GetSources: function(property, target, truthValue) {
  6374.     return this._inner.GetSources(property, target, truthValue);
  6375.   },
  6376.   
  6377.   /**
  6378.    * Gets an URL to a theme's image file
  6379.    * @param   item
  6380.    *          The RDF Resource representing the item 
  6381.    * @param   fileName
  6382.    *          The file to locate a URL for
  6383.    * @param   fallbackURL
  6384.    *          If the location fails, supply this URL instead
  6385.    * @returns An RDF Resource to the URL discovered, or the fallback
  6386.    *          if the discovery failed. 
  6387.    */
  6388.   _getThemeImageURL: function(item, fileName, fallbackURL) {
  6389.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6390.     var installLocation = this._em.getInstallLocation(id);
  6391.     var file = installLocation.getItemFile(id, fileName)
  6392.     if (file.exists())
  6393.       return gRDF.GetResource(getURLSpecFromFile(file));
  6394.  
  6395.     if (id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)) {
  6396.       var jarFile = getFile(KEY_APPDIR, [DIR_CHROME, FILE_DEFAULT_THEME_JAR]);
  6397.       var url = "jar:" + getURLSpecFromFile(jarFile) + "!/" + fileName;
  6398.       return gRDF.GetResource(url);
  6399.     }
  6400.  
  6401.     return fallbackURL ? gRDF.GetResource(fallbackURL) : null;
  6402.   },
  6403.  
  6404.   /**
  6405.    * Get the em:iconURL property (icon url of the item)
  6406.    */
  6407.   _rdfGet_iconURL: function(item, property) {
  6408.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6409.     var type = this.getItemProperty(id, "type");
  6410.     if (type != -1 && type & nsIUpdateItem.TYPE_EXTENSION) {
  6411.       var hasIconURL = this._inner.hasArcOut(item, property);
  6412.       // If the download entry doesn't have a IconURL property, use a
  6413.       // generic icon URL instead.
  6414.       if (!hasIconURL || this.getItemProperty(id, "disabled") == "true")
  6415.         return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  6416.       var iconURL = this._inner.GetTarget(item, property, true);
  6417.       iconURL = iconURL.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  6418.       var uri = newURI(iconURL);
  6419.       try {
  6420.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  6421.                             .getService(Components.interfaces.nsIChromeRegistry);
  6422.         cr.convertChromeURL(uri);
  6423.       }
  6424.       catch(e) {
  6425.         // bogus URI, supply a generic icon. 
  6426.         return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  6427.       }
  6428.     }
  6429.     else if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  6430.       return this._getThemeImageURL(item, "icon.png", URI_GENERIC_ICON_THEME);
  6431.     return null;
  6432.   },
  6433.   
  6434.   /**
  6435.    * Get the em:previewImage property (preview image of the item)
  6436.    */
  6437.   _rdfGet_previewImage: function(item, property) {
  6438.     var type = this.getItemProperty(stripPrefix(item.Value, PREFIX_ITEM_URI), "type");
  6439.     if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  6440.       return this._getThemeImageURL(item, "preview.png", null);
  6441.     return null;
  6442.   },
  6443.   
  6444.   /**
  6445.    * If we're in safe mode, the item is disabled by the user or app, or the
  6446.    * item is to be upgraded force the generic about dialog for the item.
  6447.    */
  6448.   _rdfGet_aboutURL: function(item, property) {
  6449.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6450.     if (inSafeMode() || this.getItemProperty(id, "disabled") == "true" ||
  6451.         this.getItemProperty(id, "opType") == OP_NEEDS_UPGRADE)
  6452.       return EM_L("");
  6453.  
  6454.     return null;
  6455.   },
  6456.  
  6457.   /**
  6458.    * Get the em:compatible property (whether or not this item is compatible)
  6459.    */
  6460.   _rdfGet_compatible: function(item, property) {
  6461.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6462.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  6463.     if (!targetAppInfo)
  6464.       return EM_L("false");
  6465.     getVersionChecker();
  6466.     
  6467.     var appVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  6468.                              gApp.version);
  6469.     if (gVersionChecker.compare(targetAppInfo.maxVersion, appVersion) < 0 || 
  6470.         gVersionChecker.compare(appVersion, targetAppInfo.minVersion) < 0) {
  6471.       // OK, this item is incompatible. 
  6472.       return EM_L("false");
  6473.     }
  6474.     return EM_L("true");
  6475.   }, 
  6476.   
  6477.   /** 
  6478.    * Gets the em:availableUpdateURL - the URL to an XPI update package, if
  6479.    * present, or a literal string "novalue" if there is no update XPI URL.
  6480.    */
  6481.   _rdfGet_availableUpdateURL: function(item, property) {
  6482.     var value = this._inner.GetTarget(item, property, true);
  6483.     if (!value) 
  6484.       return EM_L("none");
  6485.     return value;
  6486.   },
  6487.  
  6488.   /**
  6489.    * Get the em:displayDescription property (description that is displayed 
  6490.    * in the UI - not always the info description of the item - sometimes
  6491.    * a message like "will be uninstalled after restart" etc)
  6492.    */ 
  6493.   _rdfGet_displayDescription: function(item, property) {
  6494.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6495.  
  6496.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6497.     var itemName = this.getItemProperty(id, "name");
  6498.     var opType = this.getItemProperty(id, "opType");
  6499.     
  6500.     function getLiteral(key, strings) {
  6501.       return EM_L(extensionsStrings.formatStringFromName(key, 
  6502.                   strings, strings.length));
  6503.     }
  6504.     if (id in this._updateURLs && this._updateURLs[id]) {
  6505.       switch (this._updateURLs[id]) {
  6506.       case id:
  6507.         return getLiteral("updatingMessage", [itemName]);
  6508.       case nsIAddonUpdateCheckListener.STATUS_APP_MANAGED:
  6509.       case nsIAddonUpdateCheckListener.STATUS_NO_UPDATE:
  6510.         return getLiteral("updateNoUpdateMessage", [itemName]);
  6511.       case nsIAddonUpdateCheckListener.STATUS_VERSIONINFO:
  6512.         if (opType == OP_NEEDS_UPGRADE || opType == OP_NEEDS_INSTALL)
  6513.           break;
  6514.         return getLiteral("updateCompatibilityMessage", [itemName]);
  6515.       case nsIAddonUpdateCheckListener.STATUS_FAILURE:
  6516.         return getLiteral("updateErrorMessage", [itemName]);
  6517.       case nsIAddonUpdateCheckListener.STATUS_DISABLED:
  6518.         return getLiteral("updateDisabledMessage", [itemName]);
  6519.       case nsIAddonUpdateCheckListener.STATUS_READ_ONLY:
  6520.         return getLiteral("updateReadOnlyMessage", []);
  6521.       case nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED:
  6522.         return getLiteral("updateNotManagedMessage", [BundleManager.appName]);
  6523.       }
  6524.     }
  6525.     var node = this._inner.GetTarget(item, EM_R("availableUpdateURL"), true); 
  6526.     if (node) {
  6527.       var version = this.getItemProperty(id, "availableUpdateVersion");
  6528.       return getLiteral("updateAvailableMessage", [itemName, version]);
  6529.     }
  6530.     switch (opType) {
  6531.     case OP_NEEDS_DISABLE:
  6532.       return getLiteral("restartBeforeDisableMessage", [itemName, BundleManager.appName]);
  6533.     case OP_NEEDS_ENABLE:
  6534.       return getLiteral("restartBeforeEnableMessage", [itemName, BundleManager.appName]);
  6535.     case OP_NEEDS_INSTALL:
  6536.       return getLiteral("restartBeforeInstallMessage", [itemName, BundleManager.appName]);
  6537.     case OP_NEEDS_UNINSTALL:
  6538.       return getLiteral("restartBeforeUninstallMessage", [itemName, BundleManager.appName]);
  6539.     case OP_NEEDS_UPGRADE:
  6540.       return getLiteral("restartBeforeUpgradeMessage", [itemName, BundleManager.appName]);
  6541.     }
  6542.  
  6543.     if (this.getItemProperty(id, "appDisabled") == "true" &&
  6544.         this.getItemProperty(id, "compatible") != "true")
  6545.       return getLiteral("incompatibleExtension", [BundleManager.appName, gApp.version]);
  6546.  
  6547.     if (inSafeMode())
  6548.       return getLiteral("disabledBySafeMode", [itemName, BundleManager.appName]);
  6549.  
  6550.     // No special state for this item, so just use the "description" property. 
  6551.     return this.GetTarget(item, EM_R("description"), true);
  6552.   },
  6553.   
  6554.   /**
  6555.    * Get the em:opType property (controls widget state for the EM UI)
  6556.    * from the Startup Cache (e.g. extensions.cache)
  6557.    */
  6558.   _rdfGet_opType: function(item, property) {
  6559.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6560.     var key = this.getItemProperty(id, "installLocation");
  6561.     if (key in StartupCache.entries && id in StartupCache.entries[key] &&
  6562.         StartupCache.entries[key][id])
  6563.       return EM_L(StartupCache.entries[key][id].op);
  6564.     return EM_L(OP_NONE);
  6565.   },
  6566.  
  6567.   /**
  6568.    * Gets a localizable property. Install Manifests are generally only in one 
  6569.    * language, however an item can customize by providing localized prefs in 
  6570.    * the form:
  6571.    *
  6572.    *    extensions.{GUID}.[name|description|creator|homepageURL]
  6573.    *
  6574.    * to specify localized text for each of these properties.
  6575.    */
  6576.   _getLocalizablePropertyValue: function(item, property) {
  6577.     // These are localizable properties that a language pack supplied by the 
  6578.     // Extension may override.          
  6579.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  6580.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  6581.                     stripPrefix(property.Value, PREFIX_NS_EM);
  6582.     try {
  6583.       var value = gPref.getComplexValue(prefName, 
  6584.                                         Components.interfaces.nsIPrefLocalizedString);
  6585.       if (value.data) 
  6586.         return EM_L(value.data);
  6587.     }
  6588.     catch (e) {
  6589.     }
  6590.     return null;
  6591.   },
  6592.   
  6593.   /**
  6594.    * Get the em:name property (name of the item)
  6595.    */
  6596.   _rdfGet_name: function(item, property) {
  6597.     return this._getLocalizablePropertyValue(item, property);
  6598.   },
  6599.   
  6600.   /**
  6601.    * Get the em:description property (description of the item)
  6602.    */
  6603.   _rdfGet_description: function(item, property) {
  6604.     return this._getLocalizablePropertyValue(item, property);
  6605.   },
  6606.   
  6607.   /**
  6608.    * Get the em:creator property (creator of the item)
  6609.    */
  6610.   _rdfGet_creator: function(item, property) { 
  6611.     return this._getLocalizablePropertyValue(item, property);
  6612.   },
  6613.   
  6614.   /**
  6615.    * Get the em:homepageURL property (homepage URL of the item)
  6616.    */
  6617.   _rdfGet_homepageURL: function(item, property) {
  6618.     return this._getLocalizablePropertyValue(item, property);
  6619.   },
  6620.  
  6621.   /**
  6622.    * Get the em:disabled property. This will be true if the item has a
  6623.    * appDisabled or a userDisabled property that is true as long as it is not
  6624.    * about to be disabled.
  6625.    */
  6626.   _rdfGet_disabled: function(item, property) {
  6627.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6628.     if ((this.getItemProperty(id, "userDisabled") == "true" ||
  6629.         this.getItemProperty(id, "appDisabled") == "true") &&
  6630.         this.getItemProperty(id, "opType") != OP_NEEDS_DISABLE)
  6631.       return EM_L("true");
  6632.  
  6633.     // Migrate disabled in the extensions datasource to userDisabled or
  6634.     // appDisabled as appropriate.
  6635.     var oldDisabled = this._inner.GetTarget(item, property, true);
  6636.     if (oldDisabled instanceof Components.interfaces.nsIRDFLiteral) {
  6637.       this._inner.Unassert(item, property, oldDisabled);
  6638.       if (this.getItemProperty(id, "compatible") == "true")
  6639.         this.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  6640.       else
  6641.         this.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  6642.  
  6643.       return EM_L("true");
  6644.     }
  6645.  
  6646.     return EM_L("false");
  6647.   },
  6648.  
  6649.   /**
  6650.    * Get the em:updateable property - this specifies whether the item is
  6651.    * allowed to be updated
  6652.    */
  6653.   _rdfGet_updateable: function(item, property) {
  6654.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6655.     var opType = this.getItemProperty(id, "opType");
  6656.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  6657.         opType == OP_NEEDS_UPGRADE ||
  6658.         this.getItemProperty(id, "appManaged") == "true")
  6659.       return EM_L("false");
  6660.  
  6661.     try {
  6662.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, id)))
  6663.         return EM_L("false");
  6664.     }
  6665.     catch (e) { }
  6666.  
  6667.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  6668.     if (!installLocation || !installLocation.canAccess)
  6669.       return EM_L("false");
  6670.  
  6671.     return EM_L("true");
  6672.   },
  6673.  
  6674.   /**
  6675.    * See nsIRDFDataSource.idl
  6676.    */
  6677.   GetTarget: function(source, property, truthValue) {
  6678.     if (!source)
  6679.       return null;
  6680.       
  6681.     var target = null;
  6682.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  6683.     if (getter in this)
  6684.       target = this[getter](source, property);
  6685.  
  6686.     return target || this._inner.GetTarget(source, property, truthValue);
  6687.   },
  6688.   
  6689.   /**
  6690.    * Gets an enumeration of values of a localizable property. Install Manifests
  6691.    * are generally only in one language, however an item can customize by 
  6692.    * providing localized prefs in the form:
  6693.    *
  6694.    *    extensions.{GUID}.[contributor].1
  6695.    *    extensions.{GUID}.[contributor].2
  6696.    *    extensions.{GUID}.[contributor].3
  6697.    *    ...
  6698.    *
  6699.    * to specify localized text for each of these properties.
  6700.    */
  6701.   _getLocalizablePropertyValues: function(item, property) {
  6702.     // These are localizable properties that a language pack supplied by the 
  6703.     // Extension may override.          
  6704.     var values = [];
  6705.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  6706.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  6707.                     stripPrefix(property.Value, PREFIX_NS_EM);
  6708.     var i = 0;
  6709.     do {
  6710.       try {
  6711.         var value = gPref.getComplexValue(prefName + "." + ++i, 
  6712.                                           Components.interfaces.nsIPrefLocalizedString);
  6713.         if (value.data) 
  6714.           values.push(EM_L(value.data));
  6715.       }
  6716.       catch (e) {
  6717.         try {
  6718.           var value = gPref.getComplexValue(prefName, 
  6719.                                             Components.interfaces.nsIPrefLocalizedString);
  6720.           if (value.data) 
  6721.             values.push(EM_L(value.data));
  6722.         }
  6723.         catch (e) {
  6724.         }
  6725.         break;
  6726.       }
  6727.     }
  6728.     while (1);
  6729.     return values;
  6730.   },
  6731.   
  6732.   /**
  6733.    * Get the em:name property (name of the item - template builder calls
  6734.    * GetTargets on this one for some reason).
  6735.    */
  6736.   _rdfGets_name: function(item, property) {
  6737.     return this._getLocalizablePropertyValues(item, property);
  6738.   },
  6739.   
  6740.   /**
  6741.    * Get the em:contributor property (contributors to the extension)
  6742.    */
  6743.   _rdfGets_contributor: function(item, property) {
  6744.     return this._getLocalizablePropertyValues(item, property); 
  6745.   },
  6746.   
  6747.   /**
  6748.    * See nsIRDFDataSource.idl
  6749.    */
  6750.   GetTargets: function(source, property, truthValue) {
  6751.     if (!source)
  6752.       return null;
  6753.       
  6754.     var ary = null;
  6755.     var getter = "_rdfGets_" + stripPrefix(source.Value, EM_NS);
  6756.     if (getter in this)
  6757.       ary = this[getter](source, property);
  6758.     
  6759.     return ary ? new ArrayEnumerator(ary) 
  6760.                : this._inner.GetTargets(source, property, truthValue);
  6761.   },
  6762.   
  6763.   Assert: function(source, property, target, truthValue) {
  6764.     this._inner.Assert(source, property, target, truthValue);
  6765.   },
  6766.   
  6767.   Unassert: function(source, property, target) {
  6768.     this._inner.Unassert(source, property, target);
  6769.   },
  6770.   
  6771.   Change: function(source, property, oldTarget, newTarget) {
  6772.     this._inner.Change(source, property, oldTarget, newTarget);
  6773.   },
  6774.  
  6775.   Move: function(oldSource, newSource, property, target) {
  6776.     this._inner.Move(oldSource, newSource, property, target);
  6777.   },
  6778.   
  6779.   HasAssertion: function(source, property, target, truthValue) {
  6780.     if (!source || !property || !target)
  6781.       return false;
  6782.     return this._inner.HasAssertion(source, property, target, truthValue);
  6783.   },
  6784.   
  6785.   _observers: [],
  6786.   AddObserver: function(observer) {
  6787.     for (var i = 0; i < this._observers.length; ++i) {
  6788.       if (this._observers[i] == observer) 
  6789.         return;
  6790.     }
  6791.     this._observers.push(observer);
  6792.     this._inner.AddObserver(observer);
  6793.   },
  6794.   
  6795.   RemoveObserver: function(observer) {
  6796.     for (var i = 0; i < this._observers.length; ++i) {
  6797.       if (this._observers[i] == observer) 
  6798.         this._observers.splice(i, 1);
  6799.     }
  6800.     this._inner.RemoveObserver(observer);
  6801.   },
  6802.   
  6803.   ArcLabelsIn: function(node) {
  6804.     return this._inner.ArcLabelsIn(node);
  6805.   },
  6806.   
  6807.   ArcLabelsOut: function(source) {
  6808.     return this._inner.ArcLabelsOut(source);
  6809.   },
  6810.   
  6811.   GetAllResources: function() {
  6812.     return this._inner.GetAllResources();
  6813.   },
  6814.   
  6815.   IsCommandEnabled: function(sources, command, arguments) {
  6816.     return this._inner.IsCommandEnabled(sources, command, arguments);
  6817.   },
  6818.   
  6819.   DoCommand: function(sources, command, arguments) {
  6820.     this._inner.DoCommand(sources, command, arguments);
  6821.   },
  6822.   
  6823.   GetAllCmds: function(source) {
  6824.     return this._inner.GetAllCmds(source);
  6825.   },
  6826.   
  6827.   hasArcIn: function(node, arc) {
  6828.     return this._inner.hasArcIn(node, arc);
  6829.   },
  6830.   
  6831.   hasArcOut: function(source, arc) {
  6832.     return this._inner.hasArcOut(source, arc);
  6833.   },
  6834.   
  6835.   beginUpdateBatch: function() {
  6836.     return this._inner.beginUpdateBatch();
  6837.   },
  6838.   
  6839.   endUpdateBatch: function() {
  6840.     return this._inner.endUpdateBatch();
  6841.   },
  6842.   
  6843.   /**
  6844.    * See nsIRDFRemoteDataSource.idl
  6845.    */
  6846.   get loaded() {
  6847.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  6848.   },
  6849.   
  6850.   Init: function(uri) {
  6851.   },
  6852.   
  6853.   Refresh: function(blocking) {
  6854.   },
  6855.   
  6856.   Flush: function() {
  6857.     if (this._inner instanceof Components.interfaces.nsIRDFRemoteDataSource)
  6858.       this._inner.Flush();
  6859.   },
  6860.   
  6861.   FlushTo: function(uri) {
  6862.   },
  6863.   
  6864.   /**
  6865.    * See nsISupports.idl
  6866.    */
  6867.   QueryInterface: function(iid) {
  6868.     if (!iid.equals(Components.interfaces.nsIRDFDataSource) &&
  6869.         !iid.equals(Components.interfaces.nsIRDFRemoteDataSource) && 
  6870.         !iid.equals(Components.interfaces.nsISupports))
  6871.       throw Components.results.NS_ERROR_NO_INTERFACE;
  6872.     return this;
  6873.   }
  6874. };
  6875.  
  6876. function UpdateItem () {
  6877. }
  6878. UpdateItem.prototype = {
  6879.   /**
  6880.    * See nsIUpdateService.idl
  6881.    */
  6882.   init: function(id, version, installLocationKey, minAppVersion, maxAppVersion,
  6883.                  name, downloadURL, xpiHash, iconURL, updateURL, type) {
  6884.     this._id                  = id;
  6885.     this._version             = version;
  6886.     this._installLocationKey  = installLocationKey;
  6887.     this._minAppVersion       = minAppVersion;
  6888.     this._maxAppVersion       = maxAppVersion;
  6889.     this._name                = name;
  6890.     this._downloadURL         = downloadURL;
  6891.     this._xpiHash             = xpiHash;
  6892.     this._iconURL             = iconURL;
  6893.     this._updateURL           = updateURL;
  6894.     this._type                = type;
  6895.   },
  6896.   
  6897.   /**
  6898.    * See nsIUpdateService.idl
  6899.    */
  6900.   get id()                { return this._id;                },
  6901.   get version()           { return this._version;           },
  6902.   get installLocationKey(){ return this._installLocationKey;},
  6903.   get minAppVersion()     { return this._minAppVersion;     },
  6904.   get maxAppVersion()     { return this._maxAppVersion;     },
  6905.   get name()              { return this._name;              },
  6906.   get xpiURL()            { return this._downloadURL;       },
  6907.   get xpiHash()           { return this._xpiHash;           },
  6908.   get iconURL()           { return this._iconURL            },
  6909.   get updateRDF()         { return this._updateURL;         },
  6910.   get type()              { return this._type;              },
  6911.  
  6912.   /**
  6913.    * See nsIUpdateService.idl
  6914.    */
  6915.   get objectSource() {
  6916.     return { id                 : this._id, 
  6917.              version            : this._version, 
  6918.              installLocationKey : this._installLocationKey,
  6919.              minAppVersion      : this._minAppVersion,
  6920.              maxAppVersion      : this._maxAppVersion,
  6921.              name               : this._name, 
  6922.              xpiURL             : this._downloadURL, 
  6923.              xpiHash            : this._xpiHash,
  6924.              iconURL            : this._iconURL, 
  6925.              updateRDF          : this._updateURL,
  6926.              type               : this._type 
  6927.            }.toSource();
  6928.   },
  6929.   
  6930.   /**
  6931.    * See nsISupports.idl
  6932.    */
  6933.   QueryInterface: function(iid) {
  6934.     if (!iid.equals(Components.interfaces.nsIUpdateItem) &&
  6935.         !iid.equals(Components.interfaces.nsISupports))
  6936.       throw Components.results.NS_ERROR_NO_INTERFACE;
  6937.     return this;
  6938.   }
  6939. };
  6940.  
  6941. var gModule = {
  6942.   registerSelf: function(componentManager, fileSpec, location, type) {
  6943.     componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  6944.     
  6945.     for (var key in this._objects) {
  6946.       var obj = this._objects[key];
  6947.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  6948.                                                fileSpec, location, type);
  6949.     }
  6950.  
  6951.     // Make the Extension Manager a startup observer
  6952.     var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  6953.                                     .getService(Components.interfaces.nsICategoryManager);
  6954.     categoryManager.addCategoryEntry("app-startup", this._objects.manager.className,
  6955.                                      "service," + this._objects.manager.contractID, 
  6956.                                      true, true, null);
  6957.   },
  6958.   
  6959.   getClassObject: function(componentManager, cid, iid) {
  6960.     if (!iid.equals(Components.interfaces.nsIFactory))
  6961.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  6962.  
  6963.     for (var key in this._objects) {
  6964.       if (cid.equals(this._objects[key].CID))
  6965.         return this._objects[key].factory;
  6966.     }
  6967.     
  6968.     throw Components.results.NS_ERROR_NO_INTERFACE;
  6969.   },
  6970.   
  6971.   _makeFactory: #1= function(ctor) {
  6972.     return { 
  6973.              createInstance: function (outer, iid) {
  6974.                if (outer != null)
  6975.                  throw Components.results.NS_ERROR_NO_AGGREGATION;
  6976.                return (new ctor()).QueryInterface(iid);
  6977.              } 
  6978.            };  
  6979.   },
  6980.   
  6981.   _objects: {
  6982.     manager: { CID        : ExtensionManager.prototype.classID,
  6983.                contractID : ExtensionManager.prototype.contractID,
  6984.                className  : ExtensionManager.prototype.classDescription,
  6985.                factory    : #1#(ExtensionManager)
  6986.              },
  6987.     item:    { CID        : Components.ID("{F3294B1C-89F4-46F8-98A0-44E1EAE92518}"),
  6988.                contractID : "@mozilla.org/updates/item;1",
  6989.                className  : "Update Item",
  6990.                factory    : #1#(UpdateItem)
  6991.              }
  6992.    },
  6993.  
  6994.   canUnload: function(componentManager) {
  6995.     return true;
  6996.   }
  6997. };
  6998.  
  6999. function NSGetModule(compMgr, fileSpec) {
  7000.   return gModule;
  7001. }
  7002.  
  7003.